无需使用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
相关产品推荐
相关产品推荐

