okhttp下载文件设置进度监听
okhttp下载文件监听与 上传文件监听原理类似,上传通过requestBody的writeTo将报文发出。下载就是通过responseBody的source方法将报文接收。
仿照上传写一个responseBody
public class ProgressResponseBody extends ResponseBody {
...
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(new ProgressBufferedSource(responseBody.source()));
}
return bufferedSource;
}
class ProgressBufferedSource extends ForwardingSource {
ProgressBufferedSource(Source delegate) {
super(delegate);
}
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long readLength = super.read(sink, byteCount);
bytesRead += readLength > 0 ? readLength : 0;
progressListener.onProgress(bytesRead, responseBody.contentLength());
return readLength;
}
}
}
现在就差把这个responseBody放到这次请求当中。
网上广为流传的写法
了解okhttp的部分执行原理,它最终执行的是一个操作链Interceptors。链每个节点都会做一部分http请求操作。
通过自定义一个Interceptor,把responseBody插入执行链中。
public class ProgressResponseInterceptor implements Interceptor {
private ProgressResponseBody.ProgressListener progressListener;
public ProgressResponseInterceptor(ProgressResponseBody.ProgressListener progressListener) {
this.progressListener = progressListener;
}
@Override
public Response intercept(Chain chain) throws IOException {
Response responseResponse = chain.proceed(chain.request());
return responseResponse.newBuilder()
.body(new ProgressResponseBody(responseResponse.headers(), responseResponse.body(), progressListener))
.build();
}
}
OkHttpClient client = new OkHttpClient().newBuilder()
.addInterceptor(new ProgressResponseInterceptor(new ProgressResponseBody.ProgressListener() {
@Override
public void onProgress(long currentBytes, long contentLength) {
callback.progress(currentBytes, contentLength);
}
}))
.build();
另一种写法
经过跟踪执行,我发现了一个现象,于是我尝试换一种写法。
我把Callback方法按照代理的方式封装了一下,然后在onResponse回调中插入responseBody
class MyCallback implements Callback {
Callback delegete;
MyCallback(Callback callback){
delegete = callback;
}
@Override
public void onFailure(Call call, IOException e) {
delegete.onFailure(call, e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
delegete.onResponse(call,response.newBuilder()
.body(new ProgressResponseBody(response.headers(), response.body(), new ProgressResponseBody.ProgressListener() {
@Override
public void onProgress(long currentBytes, long contentLength) {
callback.progress(currentBytes, contentLength);
}
}))
.build());
}
}
这么写结果竟然也是对的。
我跟踪执行发现onResponse回调竟然在onProgress之前调用。这时我猜测有两种可能,一种是我监听了个寂寞,其实报文早已传输完成,监听的只不过是把内存中的数据存入外存的过程。这个经过测试感觉时间对不上不太可能。那就是另一种可能,onResponse回调执行时,responseBody还没有开始接收。
okhttp执行监听原理
我们从回调onResponse倒着找源码,先找到调用onResponse的地方
@Override protected void execute() {
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
}
}
response对象是从getResponseWithInterceptorChain中获取的。我们找到链的后一步
public final class CallServerInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
httpStream.writeRequestHeaders(request);
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpStream.finishRequest();
Response response = httpStream.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpStream.openResponseBody(response))
.build();
}
return response;
}
}
发现这里request是直接head、body全部传输到httpstream中,而response只接收了head。并没有接收body
再从response的source()方法入手
我们看BufferSource的实现类RealBufferedSource,在onResponse回调中通过is = response.body().byteStream()获取流的,这个方法获取的就是RealBufferedSource 中inputStream方法返回的流对象,这个对象中实际调用了source的read方法。
final class RealBufferedSource implements BufferedSource {
...
@Override public InputStream inputStream() {
return new InputStream() {
@Override public int read() throws IOException {
if (closed) throw new IOException("closed");
if (buffer.size == 0) {
long count = source.read(buffer, Segment.SIZE);
if (count == -1) return -1;
}
return buffer.readByte() & 0xff;
}
@Override public int read(byte[] data, int offset, int byteCount) throws IOException {
if (closed) throw new IOException("closed");
checkOffsetAndCount(data.length, offset, byteCount);
if (buffer.size == 0) {
long count = source.read(buffer, Segment.SIZE);
if (count == -1) return -1;
}
return buffer.read(data, offset, byteCount);
}
};
}
}
终于找到调用responseBody中read的位置了。
okhttp在进行网络请求的时候,responseBody是在onResponse后获取的,通过以下方法都可以触发获取responseBody。不调用这些方法在okhttp默认情况下responseBody都不会获取。
response.body().string();
is = response.body().byteStream();
is.read();
response.body().bytes();