You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何扩展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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 08:09:08