VBrowser-Android多线程下载原理:分片下载与文件合并的完整实现
VBrowser-Android多线程下载原理:分片下载与文件合并的完整实现
【免费下载链接】VBrowser-Android全网视频嗅探缓存APP项目地址: https://gitcode.com/gh_mirrors/vb/VBrowser-Android
VBrowser-Android 是一款开源的「全网视频嗅探缓存APP」,依靠超强的视频嗅探能力与多线程下载特性,能把网页中的 M3U8、MP4 等视频快速缓存到手机本地,方便随时离线观看。今天这篇文章将深入讲解VBrowser-Android 多线程下载原理,重点拆解分片下载与文件合并的完整实现,帮助新手理解一款成熟下载引擎在 Android 端是如何工作的。
为什么需要多线程下载?🧐
单线程下载一个视频时,数据是一口气"排队"流过来的:一个网络请求、一条连接、串行读取。受限于网络延迟(RTT)、TCP 拥塞窗口等瓶颈,大文件的下载速度往往无法跑满带宽。
多线程下载的思路很简单:把一个大文件切成若干段,用多个连接同时下载,最后再拼成一个完整文件。这就好比一个人搬一车砖,和 5 个人各搬一段路同时接力,速度自然天差地别。VBrowser-Android 正是把这一思路落地成了两种具体策略,分别应对两类视频格式:
| 视频类型 | 分片方式 | 合并方式 |
|---|---|---|
| 普通单文件(MP4、AVI 等) | HTTP Range 按字节切段 | FileChannel 拼接 |
| M3U8 流媒体 | 按 .ts 片段切分 | 重写播放列表,无需拼接 |
分片下载的地基:HTTP Range 协议 📡
分片下载依赖一个关键的 HTTP 能力——Range(范围)请求。客户端可以在请求头中带上:
Range: bytes=1000000-1999999服务器收到后,只返回这个字节区间的内容,返回码为 206。这样多个线程就能各取一段、互不干扰。
但并非所有服务器都支持 Range。VBrowser-Android 在正式下载前,会先通过 HEAD 请求探测服务器返回的响应头里是否包含Accept-Ranges: bytes,这一步的实现位于 HttpRequestUtil.java 的performHeadRequest方法中。
第一步:探测服务器是否支持分片下载 🧪
在 DownloadManager.java 中,detectFileSupportRange方法负责这一判断:
- 如果响应头包含
Accept-Ranges: bytes,说明服务器支持分片下载,走多线程流程; - 如果不支持,则自动降级为单线程整文件下载,保证任何视频都能下载成功,只是速度慢一些。
这个"探测—降级"的设计非常实用,也是新手学习下载器时容易忽略的一环。
第二步:按固定大小切分下载任务 ✂️
确认支持 Range 后,VBrowser-Android 会根据每片大小把文件切成若干段。这个切片大小默认是2MB(normalFileSplitSize = 2000000),可通过 AppConfig.java 中的配置调整。
切分的核心代码在 DownloadManager.java:遍历计算每个分片的起止字节,生成对应的 Range 请求头,并把任务封装后放进一个LinkedBlockingQueue 任务队列:
hashMap.put("rangeHeader", "bytes=" + n*splitSize + "-" + ((n+1)*splitSize-1)); downloadQueue.add(hashMap);一个 10MB 的视频,会被切成 5 个分片任务,依次命名为video.0、video.1、video.2……存放在临时目录中。
第三步:多线程并发下载各分片 🚀
任务队列准备好后,VBrowser-Android 会启动5 个工作线程(normalFileDownloadThreadNum,默认值)同时开工。每个线程不断从队列中取出任务:
- 发起带
Range头的 GET 请求,下载属于自己的那一段数据; - 用 1024 字节的缓冲区边读边写,落盘到临时目录的独立分片文件中;
- 下载失败会自动重试(默认最多重试 50 次),某个线程挂了不会影响其他线程;
- 队列为空时,线程自然退出。
线程从队列取任务而不是直接分配固定分片,这种"任务池 + 消费线程"的模式能天然做到负载均衡:谁先下载完谁就接着取下一个任务,慢的线程不会拖累整体进度。
第四步:文件合并的完整实现 🔗
分片都下载完成后,就进入最关键的文件合并环节,状态也切换为saving(保存中)。
VBrowser-Android 使用 Java NIO 的FileChannel + ByteBuffer完成合并,实现位于 DownloadManager.java。核心思路是:按分片序号从小到大的顺序,把每个分片文件的内容依次写入目标文件中:
for (int i = 0; i < n; i++) { fcin = new FileInputStream(tempDir + name + "." + i).getChannel(); while (true) { buffer.clear(); int r = fcin.read(buffer); // 从分片读入缓冲区 if (r == -1) break; // 读完一个分片 buffer.flip(); fcout.write(buffer); // 写入合并后的文件 } }合并完成后,删除临时分片目录,把合并好的文件重命名为video.mp4,再附带保存视频标题、类型等元信息,整个任务就圆满结束。整个过程在 FileUtil.java 的工具方法配合下完成。
M3U8 的"另类分片":每个 ts 片段都是独立文件 🎬
M3U8 是流媒体常用的播放列表格式,它本身不包含视频数据,而是引用了一长串.ts视频片段地址。因此 M3U8 的"分片下载"是天然存在的——每个 .ts 片段就是一个分片。
VBrowser-Android 的parseM3u8方法(见 DownloadManager.java)会递归解析播放列表:
- 解析列表:逐行读取 M3U8 内容,识别出所有
.ts片段地址,也支持嵌套的多码率子播放列表; - 统计总大小:先通过一个
sizeDetectQueue队列,用 HEAD 请求探测每个片段的大小并累加,从而得到整个视频的总体积,用于展示下载进度; - 并发下载:启动默认20 个工作线程(
m3U8DownloadThreadNum),从downloadQueue中取片段地址并发下载; - 处理加密:如果遇到
#EXT-X-KEY标签,会一并下载解密密钥,并把播放列表里的密钥地址重写为本地相对路径; - 重写列表:把原始播放列表中的远程地址全部替换成本地文件路径,这样播放器就能直接播放本地缓存。
M3U8 下载完成后不需要合并文件,只需把整个临时目录重命名为最终目录即可,因为播放列表已经指向了本地分片。这也是它比普通文件下载"轻"的地方。
任务调度:同时只能跑 N 个任务 🎛️
多线程下载不仅体现在单个文件内,还体现在多任务调度上。VBrowser-Android 默认最多同时进行2 个下载任务(maxConcurrentTask),超出部分进入等待队列。
调度逻辑集中在 DownloadManager.java 的addTask方法:新任务到来时,如果当前运行线程数未达上限,立即为它创建下载线程;否则放入LinkedBlockingQueue排队。每当一个任务完成或失败,就会从队列中取出下一个任务补位,并借助 EventBus 通知界面刷新下载列表。
实时进度与速度是怎么算出来的 ⏱️
细心的读者可能会好奇:下载中心的进度条和"XX MB/s"是怎么来的?
VBrowser-Android 在 DownloadTask.java 中用AtomicLong原子变量维护totalDownloaded(已下载字节数)和size(总大小),多线程并发累加也不会出错。每个下载线程每写一段数据就累加一次计数。
而实时速度由独立的speedCheckerThread计算:它每隔 1 秒读取一次lastDurationDownloadSize的增量并清零,用"每秒新增的字节数"算出瞬时速度。这样界面每秒刷新一次,就能看到平滑的下载速率了。
总结:一张表看懂两种下载策略 📋
| 对比维度 | 普通视频文件 | M3U8 流媒体 |
|---|---|---|
| 分片方式 | HTTP Range 字节切段 | 按 .ts 片段切分 |
| 默认并发线程 | 5 | 20 |
| 文件合并 | FileChannel 拼接 | 重写播放列表 |
| 临时目录 | 合并后删除 | 重命名为最终目录 |
VBrowser-Android 的多线程下载原理核心就是一句话:能用 Range 就切段并发,能拆列表就按片段并发,最后统一收尾成完整可播放的视频。理解了分片下载与文件合并的完整实现,你就掌握了大多数下载引擎的通用套路。
如果你想亲手调试这套下载引擎,可以克隆仓库到本地研读:
git clone https://gitcode.com/gh_mirrors/vb/VBrowser-Android建议按这个顺序阅读源码:先看 DownloadTask.java 了解任务模型,再看 DownloadManager.java 掌握调度与分片合并,配合 HttpRequestUtil.java 理解网络层实现,整个下载链路就完全打通了。
【免费下载链接】VBrowser-Android全网视频嗅探缓存APP项目地址: https://gitcode.com/gh_mirrors/vb/VBrowser-Android
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
