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

无需使用Async Task,如何在ExecutorService下载代码中添加进度条显示下载进度

问题核心原因

你之前添加进度条后无法下载,是因为直接在ExecutorService的子线程中操作UI控件ProgressBar,Android强制要求只有UI线程可以修改View属性,子线程操作UI会抛出CalledFromWrongThreadException,直接中断下载流程。

实现方案

通过自定义回调接口传递下载进度、成功、失败状态,在回调中切换到主线程更新进度条即可,不需要依赖AsyncTask。


步骤1:定义下载状态回调接口

public interface DownloadProgressListener {
    // 进度更新,progress范围0-100
    void onProgressUpdate(int progress);
    // 下载成功,返回保存的文件路径
    void onDownloadSuccess(String savePath);
    // 下载失败,返回错误信息
    void onDownloadFailed(String errorMsg);
}

步骤2:修改下载任务类

private class ExecutorServiceDownload implements Runnable {
    private String url;
    private DownloadProgressListener listener;

    public ExecutorServiceDownload(String url, DownloadProgressListener listener) {
        this.url = url;
        this.listener = listener;
    }

    @Override
    public void run() {
        String result = dlFile(url);
        // 处理下载结果
        if (result == null) {
            String savePath = Environment.getExternalStorageDirectory().toString() + "/test_files" + "/" + URLUtil.guessFileName(url, null, null);
            if (listener != null) {
                listener.onDownloadSuccess(savePath);
            }
        } else {
            if (listener != null) {
                listener.onDownloadFailed(result);
            }
        }
    }

    private String dlFile(String surl) {
        InputStream input = null;
        OutputStream output = null;
        HttpURLConnection connection = null;
        try {
            URL url = new URL(surl);
            connection = (HttpURLConnection) url.openConnection();
            connection.connect();

            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                return "Server returned HTTP " + connection.getResponseCode() + " " + connection.getResponseMessage();
            }

            int contentLength = connection.getContentLength();
            // 处理服务器不返回内容长度的情况
            if (contentLength <= 0) {
                return "无法获取文件大小,无法展示进度";
            }

            input = connection.getInputStream();
            String title = URLUtil.guessFileName(String.valueOf(url), null, null);
            String saveDir = Environment.getExternalStorageDirectory().toString() + "/test_files";
            File dir = new File(saveDir);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            output = new FileOutputStream(saveDir + "/" + title);

            byte data[] = new byte[4096];
            long total = 0;
            int count;
            while ((count = input.read(data)) != -1) {
                total += count;
                output.write(data, 0, count);
                // 计算进度并回调
                if (listener != null) {
                    int progress = (int) ((total * 100) / contentLength);
                    listener.onProgressUpdate(progress);
                }
            }
        } catch (Exception e) {
            return e.toString();
        } finally {
            // 关闭资源避免泄漏
            try {
                if (output != null) output.close();
                if (input != null) input.close();
            } catch (IOException ignored) {}
            if (connection != null) connection.disconnect();
        }
        return null;
    }
}

步骤3:在页面中调用更新进度条

首先在XML中定义横向进度条:

<ProgressBar
    android:id="@+id/progress_bar"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:max="100" />

在Activity中提交任务,更新UI:

// 初始化进度条
ProgressBar progressBar = findViewById(R.id.progress_bar);
// 初始化ExecutorService,建议全局实例化不要每次都新建
ExecutorService executor = Executors.newSingleThreadExecutor();

// 提交下载任务
executor.submit(new ExecutorServiceDownload("你的下载URL", new DownloadProgressListener() {
    @Override
    public void onProgressUpdate(int progress) {
        // 切到主线程更新进度条
        runOnUiThread(() -> progressBar.setProgress(progress));
    }

    @Override
    public void onDownloadSuccess(String filePath) {
        runOnUiThread(() -> {
            progressBar.setVisibility(View.GONE);
            Toast.makeText(MainActivity.this, "下载完成:" + filePath, Toast.LENGTH_SHORT).show();
        });
    }

    @Override
    public void onDownloadFailed(String errorMsg) {
        runOnUiThread(() -> {
            progressBar.setVisibility(View.GONE);
            Toast.makeText(MainActivity.this, "下载失败:" + errorMsg, Toast.LENGTH_SHORT).show();
        });
    }
}));

// 页面销毁时停止未完成的任务,避免内存泄漏
@Override
protected void onDestroy() {
    super.onDestroy();
    if (executor != null) {
        executor.shutdownNow();
    }
}

注意事项
  • Android 6.0及以上需要动态申请WRITE_EXTERNAL_STORAGE权限,Android 10及以上建议使用分区存储,不要直接访问外部存储公共目录,可替换为context.getExternalFilesDir(null)获取应用专属目录,不需要申请存储权限。
  • 如果服务器没有返回Content-Length头,contentLength会为-1,此时无法计算百分比进度,可以改为展示已下载的字节数。
  • ExecutorService建议全局复用,不要每次下载都新建实例,避免资源浪费。

内容的提问来源于stack exchange,提问作者ghazalia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 07:18:03