OKHTTP3 简单使用(五) 下载文件

基于GET进行文件下载,写法与GET请求基本一致,只不过一般GET请求的时候是通过获取ResponseBody的String,从String中获取服务器的回调信息,而下载则是通过获取ResponseBody的InputStream,然后将InputStream保存到本地文件中

由于文件下载不仅仅涉及网络请求,还涉及文件的读写,不建议在主线程中进行,因此建议使用异步方式进行请求

文件下载-不带进度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}

@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream is = null;
try {
is = response.body().byteStream();
// 将流写入到文件
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
});

文件下载-进度

与文件上传一样,OkHttp在进行文件下载的时候默认是不提供下载进度的
文件上传的时候,是将文件写入RequestBody中进行上传,而下载的时候是在ResponseBody中读取的,所以如果想要读取文件下载的进度,和上传的时候类似,上传的时候重写了RequestBody,下载就需要对ResponseBody进行重写
与上传还有一个区别,上传的时候RequestBody是请求体,请求的时候旧直接设置了,而下载的时候ResponseBody是服务器的响应结果,并不能直接进行处理,但是OkHttp可以设置拦截器,通过拦截器就可以设置ResponseBody了

设置拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
Interceptor interceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(new CustomResponseBody())
.build();
}
};

OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(interceptor)
.build();

在Interceptor中可以看到,其返回了一个Response,这个Response就是Callback返回的Response,同时为这个Response设置一个自定义的ResponseBody,然后就可以通过自定义的Response来进行下载进度的监听

重写ResponseBody

ResponseBody分析

首先查看ResponseBody发现需要重写的方法主要有三个

1
2
3
4
5
6
7
8
9
public abstract @Nullable MediaType contentType();

/**
* Returns the number of bytes in that will returned by {@link #bytes}, or {@link #byteStream}, or
* -1 if unknown.
*/
public abstract long contentLength();

public abstract BufferedSource source();

与RequestBody对比,RequestBody中使用writeTo进行写入,而ResponseBody中使用source返回数据,所以监听BufferedSource的大小就可以获取到下载的进度
通过RequestBody和Response的源码可以发现BufferedSource其实就是一个Buffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> public ResponseBody peekBody(long byteCount) throws IOException {
> BufferedSource source = body.source();
> source.request(byteCount);
> Buffer copy = source.buffer().clone();
> // There may be more than byteCount bytes in source.buffer(). If there is, return a prefix.
> Buffer result;
> if (copy.size() > byteCount) {
> result = new Buffer();
> result.write(copy, byteCount);
> copy.clear();
> } else {
> result = copy;
> }
> return ResponseBody.create(body.contentType(), result.size(), result);
> }
>
从原RequestBody将数据读取到重写的RequestBody中

但是重写的ResponseBody是没有数据的,因此需要从原来的ResponseBody中获取
Okio类还提供了从一个Source中读取到BufferSource的方法

1
2
3
4
5
6
7
8
/**
* Returns a new source that buffers reads from {@code source}. The returned
* source will perform bulk reads into its in-memory buffer. Use this wherever
* you read a source to get an ergonomic and efficient access to data.
*/
public static BufferedSource buffer(Source source) {
return new RealBufferedSource(source);
}

以及传递类

1
2
/** A {@link Source} which forwards calls to another. Useful for subclassing. */
public abstract class ForwardingSource implements Source

通过这两个类就可以轻松将原数据写入到新的BufferedSource中

1
2
3
4
5
6
Okio.buffer(new ForwardingSource(mResponseBody.source()) {
@Override
public long read(Buffer sink, long byteCount) throws IOException {
return super.read(sink, byteCount);
}
});

然后通过read方法中的buffer就可以进行进度回调了

重写ResponseBody
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class ProgressResponseBody extends ResponseBody {
private ResponseBody mResponseBody;
private ProgressCallback mCallback;

public ProgressResponseBody(ResponseBody mResponseBody, ProgressCallback mCallback) {
this.mResponseBody = mResponseBody;
this.mCallback = mCallback;
}

@Nullable
@Override
public MediaType contentType() {
return mResponseBody.contentType();
}

@Override
public long contentLength() {
return mResponseBody.contentLength();
}

@Override
public BufferedSource source() {
return Okio.buffer(new ForwardingSource(mResponseBody.source()) {
private long allBytesRead = 0L;
private long totalLength = 0L;

@Override
public long read(Buffer sink, long byteCount) throws IOException {
if (totalLength == 0L) {
if (contentLength() == -1) {
throw new RuntimeException("File is not exist");
} else {
totalLength = contentLength();
}
}
long bytesRead = super.read(sink, byteCount);
this.allBytesRead += bytesRead == -1 ? 0 : bytesRead;
mCallback.onProgress(totalLength, this.allBytesRead);
if (totalLength == (this.allBytesRead)) {
mCallback.onFinished();
}
return bytesRead;
}
});
}
}

在构造方法中传入一个ResponseBody和进度回调,传入的ResponseBody就是原ResponseBody,需要从中读取数据

完整调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Interceptor interceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(new ProgressResponseBody(originalResponse.body(), startPoint, progressCallback))
.build();
}
};

OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(interceptor)
.build();

Request request = new Request.Builder()
.url(url)
.build();

Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}

@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream is = null;
try {
is = response.body().byteStream();
// 将流写入到文件
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
});