如何扩展OKHTTP的Dispatcher为批量下载添加进度条?
嘿,这个问题问得特别接地气!先给你直接划重点:单纯扩展Dispatcher没法实现下载进度监听——因为Dispatcher的核心工作是管请求的调度(比如分配线程、控制并发数、排队待执行的任务),它根本碰不到具体的下载数据流传输过程。不过别慌,咱们可以结合OkHttp的其他组件来搞定批量下载的进度条,还能和Dispatcher的任务管理完美配合。
为什么扩展Dispatcher行不通?
首先得搞清楚Dispatcher的定位:它是OkHttp的任务调度器,负责维护请求队列、管理线程池,决定哪个请求该什么时候执行。但它完全不参与请求的实际处理——比如发送请求、读取响应体这些活,都交给Call、Interceptor、ResponseBody这些组件了。所以进度统计需要从响应体的流读取环节入手,而不是Dispatcher。
正确的实现方案:Interceptor + 自定义ResponseBody
核心思路是:通过拦截器替换原始的ResponseBody,在读取流的时候统计已读字节数,从而计算下载进度。同时用Dispatcher正常管理批量下载的并发和队列。
1. 自定义带进度监听的ResponseBody
我们需要包装原始的ResponseBody,重写读取流的方法,在每次读取数据时触发进度回调:
public class ProgressResponseBody extends ResponseBody { private final ResponseBody originalBody; private final ProgressListener listener; private BufferedSource bufferedSource; public ProgressResponseBody(ResponseBody originalBody, ProgressListener listener) { this.originalBody = originalBody; this.listener = listener; } @Override public MediaType contentType() { return originalBody.contentType(); } @Override public long contentLength() { return originalBody.contentLength(); } @Override public BufferedSource source() { if (bufferedSource == null) { bufferedSource = Okio.buffer(new ProgressSource(originalBody.source())); } return bufferedSource; } private class ProgressSource extends ForwardingSource { long totalBytesRead = 0; ProgressSource(Source delegate) { super(delegate); } @Override public long read(Buffer sink, long byteCount) throws IOException { long bytesRead = super.read(sink, byteCount); // 更新已读字节数,bytesRead为-1表示读取完成 totalBytesRead += bytesRead != -1 ? bytesRead : 0; // 回调进度,注意处理contentLength为-1(未知文件大小)的情况 listener.onProgress(totalBytesRead, originalBody.contentLength(), bytesRead == -1); return bytesRead; } } // 进度回调接口 public interface ProgressListener { void onProgress(long bytesRead, long contentLength, boolean done); } }
2. 编写拦截器替换ResponseBody
用Interceptor拦截所有请求,把原始的ResponseBody替换成我们自定义的带进度监听的版本:
public class ProgressInterceptor implements Interceptor { private final ProgressResponseBody.ProgressListener listener; public ProgressInterceptor(ProgressResponseBody.ProgressListener listener) { this.listener = listener; } @Override public Response intercept(Chain chain) throws IOException { Response originalResponse = chain.proceed(chain.request()); // 替换ResponseBody return originalResponse.newBuilder() .body(new ProgressResponseBody(originalResponse.body(), listener)) .build(); } }
3. 结合Dispatcher实现批量下载+进度监听
现在我们可以用Dispatcher控制批量下载的并发数,同时给每个任务绑定进度回调:
// 1. 配置Dispatcher,设置最大并发下载数 Dispatcher downloadDispatcher = new Dispatcher(); downloadDispatcher.setMaxRequests(5); // 最多同时跑5个下载任务 // 2. 定义进度回调——每个任务可以单独创建,也可以统一管理 ProgressResponseBody.ProgressListener progressListener = (bytesRead, contentLength, done) -> { if (contentLength != -1) { int progress = (int) ((bytesRead * 100) / contentLength); // 这里可以更新UI、记录日志,Android环境下记得切主线程 System.out.println("当前下载进度: " + progress + "%"); } if (done) { System.out.println("这个任务下载完成啦!"); } }; // 3. 创建带拦截器和自定义Dispatcher的OkHttpClient OkHttpClient downloadClient = new OkHttpClient.Builder() .dispatcher(downloadDispatcher) .addInterceptor(new ProgressInterceptor(progressListener)) .build(); // 4. 批量发起下载请求 List<String> downloadUrlList = Arrays.asList( "https://example.com/file1.zip", "https://example.com/file2.zip", "https://example.com/file3.zip" ); for (String url : downloadUrlList) { Request downloadRequest = new Request.Builder() .url(url) .build(); // 加入Dispatcher的调度队列 downloadClient.newCall(downloadRequest).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { System.out.println("下载失败: " + e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { // 把响应体写入本地文件 File saveFile = new File("downloads/" + new File(url).getName()); try (InputStream inputStream = response.body().byteStream(); FileOutputStream outputStream = new FileOutputStream(saveFile)) { byte[] buffer = new byte[8192]; int readLen; while ((readLen = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, readLen); } } } } }); }
额外小贴士
- 如果每个下载任务需要独立的进度跟踪,只需要在创建
ProgressInterceptor或者每个请求时传入不同的ProgressListener实例即可。 - 如果你需要暂停/恢复下载,Dispatcher本身支持通过
call.cancel()取消单个任务,或者dispatcher.cancelAll()取消所有任务;恢复的话需要重新发起请求。 - Android平台下,进度回调默认是在子线程执行的,更新UI时记得用
Handler、LiveData或者runOnUiThread切换到主线程。
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

