简介
我们下载文件或者其他内容时,常常都是通过Http协议直接请求整个文件。所以常见的方法是直接判断服务器返回的状态码,如果返回200,就开始写。所谓断点续传,就是指不请求整个文件,而是请求部分文件,如果服务器支持分段请求,则返回相应的分段内容,否则返回整个文件
。我们可以通过断点续传来实现多条线程来下载同一个文件,加快速度;也可以通过断点续传实现暂停/恢复下载的功能,试想,如果没有断点请求,每次都是请求整个文件,又怎么能从上次暂停处恢复呢?
HTTP
和断点续传相关的HTTP内容主要有:
- 断点请求头部
- 206状态码
1、请求
当我们向服务器进行断点请求时,只需要在http请求上面添加下面的请求头部即可:
connection.setRequestProperty("Range", "bytes=0-");
Range头部有几种格式,注意是从0开始计数:
bytes=0-
表示请求第0个字节到文件的最后一个字节;
bytes=10-100
表示请求第10个字节到第100个字节;
bytes=-100
表示请求第0个字节到第100个字节。
2、响应
我们在请求行加入上诉请求头部,这也是请求完整报文,但是与不加这个头部有什么不同呢?
如果服务器支持断点请求,此时返回的状态码是206
,并且返回响应头部带有Content-Range=[bytes 0-6405598/6405599]
的分段信息,很明显这个头部表示分段的请求区间以及整个文件的长度。
而如果服务器不支持断点请求时,返回的状态码是200,和普通的请求相同。
所以我们可以简单地通过状态码是否为206来判断服务器是否支持断点下载。
写
如果没有断点请求,我们一般都直接用FileOutputStream开始写整个文件了,不过既然是断点,那得写在特定的区间啊,可以通过RandomAccessFile来写特定区间。
randomAccessFile = new RandomAccessFile(savePath + saveName, "rw");
randomAccessFile.seek(startPos);
while ((len = inputStream.read(buffer)) != -1){
randomAccessFile.write(buffer, 0, len);
}
上诉片段代码就代表从startPos开始写内容
思路
一开始我们提到断点续传可以实现:
- 多条线程来下载同一个文件,加快速度;
- 暂停/恢复下载的功能。
这里基于服务器支持断点请求来讲一下多线程下载的思路,这个明白了暂停/恢复也是一回事。
假设我们请求的文件长度为len,需要n条线程来下载。大致步骤:
①计算每条线程的请求区间:
int segment = len / n;
for(int i = 0; i < n; i++){
ranges[i].startPos = i * segment; // Range数组,Range类有startPos和endPos两个属性
ranges[i].endPos = startPos + segment - 1;
if(i == n-1){
ranges[i].endPos = len-1;
}
}
②n条线程去请求
String range = "bytes=" + ranges[i].startPos + "-" + ranges[i].endPos;
connection.setRequestProperty("Range", range);
③写
randomAccessFile = new RandomAccessFile(savePath + saveName, "rw");
randomAccessFile.seek(ranges[i].startPos); // 先定位区间起始位置
while ((len = inputStream.read(buffer)) != -1){
randomAccessFile.write(buffer, 0, len); // 开始写
}
这样我们就不再是请求整个文件,而是多条线程多个分段去下载了。
源码
上面只讲了多线程下载的思路,关于暂停恢复其关键点在于randomAccessFile.seek(startPos),只要记录好已下的长度,下次直接请求[已下,目的]长度,然后从已下的位置开始写即可。
基于断点续传写了一个小型的库,支持:
- 暂停下载
- 恢复下载
- 下载回调
- 通过回调获得下载任务的总长度、已下长度、耗时、进度、平均下载速度等…
- …