在 Android 應用中實現「下載檔案並即時顯示進度」是常見需求。本文詳細比較三種主流方案:AsyncTask(傳統)、IntentService(後台服務)、DownloadManager(系統級),並提供完整可執行程式碼範例。
優點:程式碼集中、易理解、可直接更新 UI。
缺點:Activity 旋轉/銷毀會導致 Task 洩漏、無法支援斷點續傳、Android 11+ 已棄用。
// Activity 成員變數
private ProgressDialog mProgressDialog;
private void startDownload(String url) {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage("下載中...");
mProgressDialog.setIndeterminate(false);
mProgressDialog.setMax(100);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setCancelable(false);
new DownloadTask().execute(url);
}
private class DownloadTask extends AsyncTask {
@Override
protected void onPreExecute() {
mProgressDialog.show();
}
@Override
protected String doInBackground(String... urls) {
String urlStr = urls[0];
try {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
int fileLength = conn.getContentLength();
InputStream input = new BufferedInputStream(conn.getInputStream());
File outputFile = new File(getExternalFilesDir(null), "download.apk");
OutputStream output = new FileOutputStream(outputFile);
byte[] buffer = new byte[4096];
long total = 0;
int count;
while ((count = input.read(buffer)) != -1) {
if (isCancelled()) break;
total += count;
if (fileLength > 0) {
publishProgress((int) (total * 100 / fileLength));
}
output.write(buffer, 0, count);
}
output.flush();
output.close();
input.close();
return outputFile.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onProgressUpdate(Integer... progress) {
mProgressDialog.setProgress(progress[0]);
}
@Override
protected void onPostExecute(String result) {
mProgressDialog.dismiss();
if (result != null) {
Toast.makeText(MainActivity.this, "下載完成:" + result, Toast.LENGTH_LONG).show();
// 安裝 APK 或開啟檔案
} else {
Toast.makeText(MainActivity.this, "下載失敗", Toast.LENGTH_SHORT).show();
}
}
}
優點:獨立於 Activity 生命週期、支援多 Activity 監聽進度、自動在背景執行緒執行。
缺點:程式碼較複雜、Android 8+ 背景執行限制需改用 JobIntentService 或 WorkManager。
IntentService,onHandleIntent 執行下載並透過 ResultReceiver 回報進度。ResultReceiver 子類別,覆寫 onReceiveResult 更新 UI。// DownloadService.java
public class DownloadService extends IntentService {
public static final int UPDATE_PROGRESS = 8344;
public DownloadService() { super("DownloadService"); }
@Override
protected void onHandleIntent(Intent intent) {
String url = intent.getStringExtra("url");
ResultReceiver receiver = intent.getParcelableExtra("receiver");
// ... 下載邏輯同 AsyncTask ...
// 關鍵:每段下載後
Bundle bundle = new Bundle();
bundle.putInt("progress", (int)(total * 100 / fileLength));
receiver.send(UPDATE_PROGRESS, bundle);
// 完成時
bundle.putInt("progress", 100);
receiver.send(UPDATE_PROGRESS, bundle);
}
}
// Activity 中啟動
Intent intent = new Intent(this, DownloadService.class);
intent.putExtra("url", downloadUrl);
intent.putExtra("receiver", new DownloadReceiver(new Handler()));
startService(intent);
// DownloadReceiver 內部類
private class DownloadReceiver extends ResultReceiver {
public DownloadReceiver(Handler handler) { super(handler); }
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == DownloadService.UPDATE_PROGRESS) {
int progress = resultData.getInt("progress");
mProgressDialog.setProgress(progress);
if (progress == 100) mProgressDialog.dismiss();
}
}
}
優點:系統自動處理執行緒、斷點續傳、網路切換、通知列進度、媒體掃描、電源管理、權限隔離。
缺點:自訂 UI 困難(進度只在通知列)、無法直接獲取進度回調更新自訂 ProgressBar。
public static void startSystemDownload(Context context, String url, String fileName, String title, String description) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setTitle(title);
request.setDescription(description);
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
request.allowScanningByMediaScanner(); // 讓媒體掃描器發現
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);
}
| 場景 | 推薦方案 |
|---|---|
| 簡單 App 內下載、需自訂 ProgressBar、檔案較小 | AsyncTask(或更現代的 Coroutine + Flow) |
| 大檔案、需背景下載、支援斷點續傳、跨 Activity 監控 | WorkManager + 自訂 Worker(現代替代 IntentService) |
| APK 更新、媒體檔案、希望系統通知列顯示進度、最省開發力 | DownloadManager |
Android 11+ 棄用 AsyncTask,Android 8+ 限制 IntentService。新專案建議使用 WorkManager 配合 Kotlin Coroutine 與 Flow 實現可觀測的下載進度:
// DownloadWorker.kt
class DownloadWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
val url = inputData.getString("url") ?: return Result.failure()
val fileName = inputData.getString("fileName") ?: "download"
val outputFile = File(applicationContext.getExternalFilesDir(null), fileName)
val progressChannel = Channel(Channel.CONFLATED)
// 使用 suspend 函式下載並發送進度
val success = downloadWithProgress(url, outputFile, progressChannel)
// 透過 ForegroundInfo 顯示通知列進度(可選)
return if (success) Result.success() else Result.failure()
}
}
<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />(Android 10+ 改用 Scoped Storage)<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />(檢查網路狀態)Range: bytes=xxx-AsyncTask.cancel(true) 或 WorkManager.cancelWorkById()選對方案、處理好邊界情況,才能打造穩健、使用者體驗良好的下載功能。