最近在做一个下载文件的功能的时候,因为要支持断点续传,虽然整体上思路很清晰,也比较简单,但是在做的过程中还是遇到了几个坑,特意在此记录一下,也重新梳理下下载断点续传的实现过程。
一、要注意的地方这里先把几个坑点先说明一下:
1. 追加文件如果你写入文件用的是FileOutputStream,那么一定要用两参的构造方法:
```java
new FileOutputStream(file,true); // 第二个参数为true
```
2. 跳过输入流
当你用方法1(通过inputStream的skip)去实现断点续传的时候,skip方法有坑,简单来说就是skip方法不能保证一定会跳过你指定的字节数,所以你自己要做一个简单处理:
private void skip(InputStream in, long offset) throws IOException {
long skip = 0;
do {
offset -= skip;
skip = in.skip(offset);
if (skip <= 0) {
break;
}
} while (skip != offset);
}
3.range header
当你用方法2(http range header)去实现断点续传的时候,有如下三个坑:
坑点1.首先要确保服务器支持断点续传,我当时就是因为从网上先随便找个文件来下载,一些文件的服务器是不支持的,导致我调试了好久好久。。。那么怎么判断服务器是否支持呢? 当然是问服务器的同学啦,如果他们也不确定的话(又一个坑~),求人不如求己,可以通过如下命令查看:
curl -i --range 100-200 http://app.znds.com/down/20200331/w2zry_1.53.1.6_dangbei.apk
看它返回的数据
HTTP/1.1 206 Partial Content
Server: JSP3/2.0.14
Date: Fri, 24 Apr 2020 04:21:54 GMT
Content-Type: application/vnd.android.package-archive
Content-Length: 101
Connection: keep-alive
ETag: "7629026C24EBCA9B219A6BEC4DA7E475"
Last-Modified: Tue, 31 Mar 2020 08:02:33 GMT
Age: 863675
Content-Range: bytes 100-200/393647401
Accept-Ranges: bytes
x-oss-request-id: 5E82FA10146F3833363BA5BA
x-oss-object-type: Normal
x-oss-hash-crc64ecma: 14417895789503488492
x-oss-storage-class: Standard
Content-MD5: dikCbCTrypshmmvsTafkdQ==
x-oss-server-time: 9
Ohc-File-Size: 393647401
Timing-Allow-Origin: *
Ohc-Cache-HIT: cangzuncache63 [4], qdix131 [3]
如果也Content-Range 和 Accept-Ranges,那说明支持,否则就别费劲了,用方法1吧。
坑点2第二个坑点是,给request设置header的时候,代码如下:
header("Range", "bytes=" + offset + "-")
一定不能缺少最后的那个"-",否则这个就是个无效的header。
坑点3.第三个坑点是,当你给你的request添加了这个header之后,你要计算下载百分比吧,我之前计算百分比的分母(即文件大小)的时候,用的是Content-length里返回的数据,那么问题就来了,当你断点续传的时候,返回的数据流里不是所有数据的大小,所以content-length肯定就变小了,这时你在计算百分比肯定就错误了(好吧,这是我自己的问题),解决办法有两个,第一个还是每次都取content-length,但是不能直接用来做分母,要和本地文件的大小做一个加和;第二个就是服务器直接在反水的数据里告诉你文件大小,不取content-length。
二、代码这里我还用了OKhttp做网络请求。
下面两种方法我都是自己传进来了文件的大小,如果不想自己传的话,下面的size可以改成,contentLength自己去获取吧。
long size = contentLength + offset;
方法1
优点:不依赖服务器的支持,完全端上自己实现;
缺点:会浪费流量;
public void download(final String url, final String path, long offset,
long size, final OnDownloadListener listener) {
Request request =
new Request.Builder()
.url(url)
.build();
downloadCall = okHttpClient.newCall(request);
downloadCall.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 下载失败
listener.onDownloadFailed(1);
}
@Override
public void onResponse(Call call, Response response) {
listener.onDownloadStart();
InputStream is = null;
byte[] buf = new byte[2048];
int len = 0;
FileOutputStream fos = null;
try {
is = response.body().byteStream();
// 跳过offset文件大小
skip(is,offset);
File file = new File(path);
fos = new FileOutputStream(file, true);
long sum = offset;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
Log.d("downloadtUtil", "download length:" + sum);
int progress = (int) (sum * 1.0f / size * 100);
// 下载中
listener.onDownloading(progress);
}
fos.flush();
// 下载完成
listener.onDownloadSuccess();
} catch (SocketException e) {
e.printStackTrace();
listener.onDownloadFailed(1);
} catch (IOException e) {
e.printStackTrace();
listener.onDownloadFailed(0);
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
}
}
}
});
}
方法2
优点:节省流量,已经下载过的数据不再下载了,真正的断点续传;
缺点:依赖服务器配置或者服务器开发人员开发;
public void download(final String url, final String path, long offset,
long size, final OnDownloadListener listener) {
Request request =
new Request.Builder()
.header("Range", "bytes=" + offset + "-")
.url(url)
.build();
downloadCall = okHttpClient.newCall(request);
downloadCall.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 下载失败
listener.onDownloadFailed(1);
}
@Override
public void onResponse(Call call, Response response) {
HLog.d("http request:" + response.request().headers());
HLog.d("http request:" + response.headers());
listener.onDownloadStart();
InputStream is = null;
byte[] buf = new byte[2048];
int len = 0;
FileOutputStream fos = null;
// 储存下载文件的目录
try {
is = response.body().byteStream();
File file = new File(path);
fos = new FileOutputStream(file, true);
long sum = offset;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
Log.d("downloadtUtil", "download length:" + sum);
int progress = (int) (sum * 1.0f / size * 100);
// 下载中
listener.onDownloading(progress);
}
fos.flush();
// 下载完成
listener.onDownloadSuccess();
} catch (SocketException e) {
e.printStackTrace();
listener.onDownloadFailed(1);
} catch (IOException e) {
e.printStackTrace();
listener.onDownloadFailed(0);
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
}
}
}
});
}
作者:宇光十色_FLY