Android DownloadManager 全面解析与使用指南
DownloadManager 是 Android 2.3(API 级别 9)引入的系统级服务,专门用于处理长时间运行的下载操作。它提供了一个健壮的后台下载机制,能够自动处理 HTTP 连接、在连接更改或系统重启后恢复下载,并管理重试逻辑。
博主博客
核心特性
- 后台服务:在系统后台执行下载,应用无需保持活跃状态
- 自动恢复:处理网络中断、连接变更和系统重启后的下载恢复
- 通知集成:自动显示下载进度通知
- 下载队列:支持多个下载任务的排队管理
- 内容提供者接口:通过 ContentProvider 暴露下载状态信息
获取 DownloadManager 实例
// 方式一:使用 Class 参数(推荐,类型安全)
DownloadManager downloadManager = getSystemService(DownloadManager.class);
// 方式二:使用 String 参数
DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
权限声明
在 AndroidManifest.xml 中添加必要权限:
<!-- 必需权限:网络访问 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Android 10 之前需要的外部存储权限 -->
<!-- 注意:从 Android 10 (API 29) 开始,Scoped Storage 限制了对外部存储的直接访问 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
注意:从 Android 6.0 (API 23) 开始,需要在运行时请求危险权限(如 WRITE_EXTERNAL_STORAGE)。
核心类与方法
1. DownloadManager.Request
包含下载请求的配置信息。
// 创建下载请求
Uri uri = Uri.parse("https://example.com/file.apk");
DownloadManager.Request request = new DownloadManager.Request(uri);
// 设置下载路径(Android 10+ 注意存储权限限制)
request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
"myapp.apk"
);
// 可选配置
request.setTitle("应用更新") // 通知标题
.setDescription("正在下载最新版本") // 通知描述
.setNotificationVisibility( // 通知可见性
DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setAllowedNetworkTypes( // 允许的网络类型
DownloadManager.Request.NETWORK_WIFI |
DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(false) // 是否允许漫游时下载
.setVisibleInDownloadsUi(true) // 在系统下载UI中显示
.setMimeType("application/vnd.android.package-archive"); // MIME类型
2. DownloadManager.Query
用于查询下载状态和信息。
3. 主要方法
enqueue(Request request):将下载请求加入队列,返回唯一的下载IDquery(Query query):查询下载信息remove(long... ids):删除下载记录并取消下载openDownloadedFile(long id):打开已下载的文件getUriForDownloadedFile(long id):获取已下载文件的Uri
完整使用示例
1. 初始化下载
public class DownloadHelper {
private DownloadManager downloadManager;
private Context context;
public DownloadHelper(Context context) {
this.context = context.getApplicationContext();
this.downloadManager = (DownloadManager)
context.getSystemService(Context.DOWNLOAD_SERVICE);
}
public long startDownload(String url, String fileName) {
// 检查下载管理器是否可用
if (!isDownloadManagerEnabled(context)) {
showDownloadManagerDisabledDialog();
return -1;
}
Uri uri = Uri.parse(url);
DownloadManager.Request request = new DownloadManager.Request(uri);
// 设置下载路径
request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
fileName
);
// 配置请求
configureRequest(request, fileName);
// 加入下载队列
return downloadManager.enqueue(request);
}
private void configureRequest(DownloadManager.Request request, String fileName) {
request.setTitle(fileName)
.setDescription("文件下载中...")
.setNotificationVisibility(
DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI |
DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(false);
// 根据文件类型设置MIME类型
if (fileName.endsWith(".apk")) {
request.setMimeType("application/vnd.android.package-archive");
}
}
}
2. 监听下载进度
DownloadManager 不提供直接的进度回调,但可以通过 ContentObserver 监听数据库变化:
public class DownloadProgressObserver {
private static final Uri CONTENT_URI =
Uri.parse("content://downloads/my_downloads");
private Context context;
private DownloadManager downloadManager;
private ContentObserver contentObserver;
private Handler handler;
private long downloadId;
public DownloadProgressObserver(Context context, long downloadId) {
this.context = context;
this.downloadId = downloadId;
this.downloadManager = (DownloadManager)
context.getSystemService(Context.DOWNLOAD_SERVICE);
this.handler = new Handler(Looper.getMainLooper());
initContentObserver();
}
private void initContentObserver() {
contentObserver = new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
queryDownloadProgress();
}
};
context.getContentResolver().registerContentObserver(
CONTENT_URI,
true,
contentObserver
);
}
private void queryDownloadProgress() {
DownloadManager.Query query = new DownloadManager.Query()
.setFilterById(downloadId);
try (Cursor cursor = downloadManager.query(query)) {
if (cursor != null && cursor.moveToFirst()) {
int status = cursor.getInt(cursor.getColumnIndexOrThrow(
DownloadManager.COLUMN_STATUS));
long downloaded = cursor.getLong(cursor.getColumnIndexOrThrow(
DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
long total = cursor.getLong(cursor.getColumnIndexOrThrow(
DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
// 更新UI
updateProgress(status, downloaded, total);
}
}
}
private void updateProgress(int status, long downloaded, long total) {
switch (status) {
case DownloadManager.STATUS_RUNNING:
// 下载中,更新进度
int progress = (total > 0) ?
(int) ((downloaded * 100) / total) : 0;
// 发送进度到UI线程
break;
case DownloadManager.STATUS_SUCCESSFUL:
// 下载完成
break;
case DownloadManager.STATUS_FAILED:
// 下载失败
break;
}
}
public void unregisterObserver() {
if (contentObserver != null) {
context.getContentResolver().unregisterContentObserver(contentObserver);
}
}
}
替代方案:定时查询(适用于快速下载或避免频繁回调)
public class ScheduledProgressChecker {
private ScheduledExecutorService executor =
Executors.newScheduledThreadPool(1);
private Runnable checkTask;
public void startChecking(long downloadId, long intervalSeconds) {
checkTask = () -> queryDownloadProgress(downloadId);
executor.scheduleAtFixedRate(
checkTask,
0,
intervalSeconds,
TimeUnit.SECONDS
);
}
public void stopChecking() {
if (executor != null && !executor.isShutdown()) {
executor.shutdown();
}
}
}
3. 监听下载完成
public class DownloadCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
long downloadId = intent.getLongExtra(
DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (downloadId != -1) {
handleDownloadComplete(context, downloadId);
}
}
}
private void handleDownloadComplete(Context context, long downloadId) {
DownloadManager dm = (DownloadManager)
context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query()
.setFilterById(downloadId);
try (Cursor cursor = dm.query(query)) {
if (cursor != null && cursor.moveToFirst()) {
int status = cursor.getInt(cursor.getColumnIndex(
DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
// 下载成功
String uriString = cursor.getString(cursor.getColumnIndex(
DownloadManager.COLUMN_LOCAL_URI));
// 处理下载的文件
} else if (status == DownloadManager.STATUS_FAILED) {
// 下载失败
int reason = cursor.getInt(cursor.getColumnIndex(
DownloadManager.COLUMN_REASON));
// 处理失败原因
}
}
}
}
}
// 在Activity/Fragment中注册
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
IntentFilter filter = new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE);
// 注意:Android 8.0+ 需要显式注册
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
registerReceiver(receiver, filter,
Context.RECEIVER_EXPORTED);
} else {
registerReceiver(receiver, filter);
}
}
4. 处理通知栏点击
public class NotificationClickReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(intent.getAction())) {
// 获取点击的下载ID数组
long[] ids = intent.getLongArrayExtra(
DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
// 打开下载管理界面或执行自定义操作
Intent viewDownloads = new Intent(
DownloadManager.ACTION_VIEW_DOWNLOADS);
viewDownloads.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(viewDownloads);
}
}
}
工作原理解析
DownloadManager 内部流程
- enqueue() 方法调用
public long enqueue(Request request) {
ContentValues values = request.toContentValues(mPackageName);
Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
return Long.parseLong(downloadUri.getLastPathSegment());
}
- 数据流:
- 将 Request 转换为 ContentValues
- 通过 ContentResolver 插入到 DownloadProvider
- 启动 DownloadService 处理下载任务
- DownloadThread 执行实际下载(使用 HttpURLConnection)
注意:从 Android N (API 24) 开始,实现方式有所变化,但对外接口保持一致。
常见问题与解决方案
1. DownloadManager 不可用或禁用
/**
* 检查下载管理器是否可用
*/
public static boolean isDownloadManagerEnabled(Context context) {
int state = context.getPackageManager()
.getApplicationEnabledSetting("com.android.providers.downloads");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return !(state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED ||
state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER ||
state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
} else {
return !(state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED ||
state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
}
}
/**
* 引导用户启用下载管理器
*/
public static void enableDownloadManager(Context context) {
try {
// 打开下载管理器的应用详情页
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:com.android.providers.downloads"));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
// 回退到应用管理页面
Intent intent = new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
2. Android 10+ 存储权限问题
// Android 10+ 推荐将文件下载到应用私有目录
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// 不需要 WRITE_EXTERNAL_STORAGE 权限
request.setDestinationInExternalFilesDir(
context,
Environment.DIRECTORY_DOWNLOADS,
fileName
);
} else {
// Android 9 及以下
request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
fileName
);
}
3. 断点续传机制
DownloadManager 通过以下方式实现断点续传:
- 监听网络连接变化广播(
ConnectivityManager.CONNECTIVITY_ACTION) - 网络恢复时重启 DownloadService
- DownloadThread 支持 Range 请求,从断点处继续下载
最佳实践
-
权限处理:
- 动态请求运行时权限(Android 6.0+)
- 处理权限拒绝情况
-
错误处理:
- 检查网络状态
- 处理存储空间不足
- 监控下载失败原因
-
状态管理:
- 保存下载ID以便恢复状态
- 清理已完成或取消的下载记录
-
用户体验:
- 提供取消下载选项
- 显示清晰的进度和状态
- 处理安装/打开下载的文件
参考资源
Android DownloadManager 全面解析与使用指南
https://blog.uso6.com/archives/android-downloadmanager-quan-mian-jie-xi-yu-shi-yong-zhi-nan
评论