学习自https://www.jianshu.com/p/df7d4945f007
网速太快,只能打印日志
02-19 01:52:10.135 3239-3252/? I/xbh: 4
02-19 01:52:10.136 3239-3252/? I/xbh: 24
02-19 01:52:10.136 3239-3252/? I/xbh: 44
02-19 01:52:10.141 3239-3252/? I/xbh: 63
02-19 01:52:10.141 3239-3252/? I/xbh: 83
02-19 01:52:10.142 3239-3252/? I/xbh: 100
02-19 01:52:10.142 3239-3252/? I/xbh: 100
BufferedSource
ResponseBody这个类(RequestBody同理),它是一个抽象类,有着三个抽象方法:
public abstract class ResponseBody implements Closeable {
//返回响应内容的类型,比如image/jpeg
public abstract MediaType contentType();
//返回响应内容的长度
public abstract long contentLength();
//返回一个BufferedSource
public abstract BufferedSource source();
//...
}
响应体有3个抽象方法,重点关注下BufferedSource。
在网络读取响应体的时候,Okio首先会把响应体读到缓冲区中,即BufferedSource。
所以我们监听的原理就是read缓冲区这个过程。我认为这可能是伪实现,是错误的。因为onResponse只会回调一次,所以他的原理应该是等待所有的数据都下载到了缓冲区后,再进行一个read,监听的其实就是read。但是还有一个可能,onResponse回调的时机是第一批数据到达的时候,这个情况下就可以真正监听到。所以分析了一下,应该是后者是对的。这个问题可能在了解了断点续传的原理后,能够得到解答。
重写source
@Override
public BufferedSource source() {
if (bufferedSource == null){
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source){
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink,byteCount);
totalBytesRead += bytesRead != -1 ? bytesRead : 0; //不断统计当前下载好的数据
//接口回调
progressListener.update(totalBytesRead,responseBody.contentLength(),bytesRead == -1);
return bytesRead;
}
};
}
本来通过Okio.buffer(responseBody.source())这个api就可以得到bufferedSource,但是这样无法监听到read,所以用ForwardingSource进行包装,使得我们可以监听read。
全部代码
public class ProgressResponseBody extends ResponseBody{ private final ResponseBody responseBody; private BufferedSource bufferedSource; private final ProgressListener progressListener; public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener){ this.responseBody = responseBody; this.progressListener = progressListener; } @Nullable @Override public MediaType contentType() { return responseBody.contentType(); } @Override public long contentLength() { return responseBody.contentLength(); } @Override public BufferedSource source() { if (bufferedSource == null){ bufferedSource = Okio.buffer(source(responseBody.source())); } return bufferedSource; } private Source source(Source source){ return new ForwardingSource(source) { long total = 0L; @Override public long read(@NonNull Buffer sink, long byteCount) throws IOException { long bytesRead = super.read(sink,byteCount); total += bytesRead != -1 ? bytesRead : 0; progressListener.update((int)(((float)total / responseBody.contentLength()) * 100)); return bytesRead; } }; } public interface ProgressListener { void update(int percent); } }
@BaseActivity.LayoutId(id = R.layout.activity_main) public class MainActivity extends BaseActivity { private static final String TAG = "xbh"; @Override protected void init() { final ProgressBar pb = findViewById(R.id.pb); Request request = new Request.Builder() .url("https://img-my.csdn.net/uploads/201603/26/1458988468_5804.jpg") .build(); OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(new Interceptor() { @Override public Response intercept(@NonNull Chain chain) throws IOException { Response response = chain.proceed(chain.request()); return response.newBuilder() .body(new ProgressResponseBody(response.body(), new ProgressResponseBody.ProgressListener() { @Override public void update(int percent) { pb.setProgress(percent); } })) .build(); } }).build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { Log.i(TAG, e.getMessage()); } @Override public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { InputStream inputStream = response.body().byteStream(); FileOutputStream fileOutputStream; File file = new File(SDUtils.getPath(),"666.txt"); fileOutputStream = new FileOutputStream(file); byte[] buffer= new byte[2048]; int len; while ((len= inputStream.read(buffer)) != -1) { fileOutputStream.write(buffer, 0, len); } fileOutputStream.flush(); fileOutputStream.close(); } }); } }拦截器不太明白的话,可以看看http://blog.csdn.net/qq_36523667/article/details/79335716,很简单的
原理
分析下string(),就是输入流转String这个常用的api
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}
RealBufferedSource#readString(charset),是BufferedSource的实现类
@Override public String readString(Charset charset) throws IOException {
if (charset == null) throw new IllegalArgumentException("charset == null");
buffer.writeAll(source);//把输入流的内容写到buffer缓冲区中
return buffer.readString(charset);
}
Buffer#writeAll(Source)
@Override public long writeAll(Source source) throws IOException {
if (source == null) throw new IllegalArgumentException("source == null");
long totalBytesRead = 0;
for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) {//ForwardingSource的source方法
totalBytesRead += readCount;
}
return totalBytesRead;
}
所以原理就是监听你读缓冲区的情形。如果你对输入流不进行任何操作,那么你的进度永远是0。