Android使用DownloadManager单通知逐个下载多文件并显示整体进度
可行性分析与实现方案
当然可行!不过默认的DownloadManager不会直接提供这个功能——它每个下载任务都会创建独立的通知。要实现单通知展示多文件的整体进度,你需要结合DownloadManager的监听机制+自定义通知来搞定,下面是具体的思路和实现步骤:
核心思路
通过监听所有下载任务的进度变化,实时计算已下载总字节数与所有文件的总大小的比例,然后用自定义通知展示这个整体进度,同时关闭DownloadManager默认的单个任务通知。
具体实现步骤
1. 提前计算总下载大小
在发起所有下载请求前,先获取每个文件的总大小(可以通过Http请求头的Content-Length获取),累加得到总下载大小。如果有些文件无法提前获取大小(比如服务器未返回长度),可以后续通过DownloadManager的查询接口补全,或者做兼容处理(比如按已下载比例估算)。
2. 关闭DownloadManager默认通知
给每个DownloadManager.Request设置隐藏通知,避免系统弹出单个任务的通知:
val request = DownloadManager.Request(Uri.parse(fileUrl)) .setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) // 隐藏默认通知 // 其他配置:存储路径、标题等
3. 监听所有下载任务的进度变化
注册ContentObserver监听DownloadManager的内容变化,每次变化时查询所有任务的已下载字节数,计算整体进度:
// 自定义ContentObserver,监听下载进度变化 class DownloadBatchObserver(handler: Handler, private val updateCallback: () -> Unit) : ContentObserver(handler) { override fun onChange(selfChange: Boolean) { super.onChange(selfChange) updateCallback.invoke() // 触发进度计算 } } // 在Activity/Fragment或Service中注册监听 val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager val observer = DownloadBatchObserver(Handler(Looper.getMainLooper())) { calculateOverallProgress() } contentResolver.registerContentObserver(DownloadManager.Query.CONTENT_URI, true, observer)
4. 计算整体进度并更新自定义通知
编写方法查询所有下载任务的进度,计算整体比例,然后更新自定义通知的进度条:
// 保存所有发起的下载任务ID,避免查询到其他App的任务 private val downloadTaskIds = mutableListOf<Long>() private fun calculateOverallProgress() { val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager val query = DownloadManager.Query().setFilterById(*downloadTaskIds.toLongArray()) val cursor = downloadManager.query(query) var totalDownloaded = 0L var totalSize = 0L if (cursor.moveToFirst()) { do { val downloaded = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) val total = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) totalDownloaded += downloaded totalSize += total } while (cursor.moveToNext()) } cursor.close() val progress = if (totalSize == 0) 0 else (totalDownloaded * 100 / totalSize).toInt() updateBatchNotification(progress, totalDownloaded, totalSize) } private fun updateBatchNotification(progress: Int, downloaded: Long, total: Long) { val channelId = "BATCH_DOWNLOAD_CHANNEL" // 适配Android 8.0+的通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel(channelId, "批量下载通知", NotificationManager.IMPORTANCE_LOW) val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } val notificationBuilder = NotificationCompat.Builder(this, channelId) .setSmallIcon(R.drawable.ic_download) .setContentTitle("批量下载中") .setContentText("已下载 ${formatFileSize(downloaded)} / ${formatFileSize(total)}") .setProgress(100, progress, false) // 显示精确进度条 .setOngoing(true) // 标记为正在进行的通知,避免被误删 // 全部下载完成时更新通知状态 if (progress == 100) { notificationBuilder.setContentText("全部下载完成") .setOngoing(false) .setAutoCancel(true) } val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(10001, notificationBuilder.build()) // 固定通知ID,确保更新同一个通知 } // 辅助方法:格式化文件大小为可读格式 private fun formatFileSize(size: Long): String { return when { size >= 1024 * 1024 * 1024 -> String.format("%.2f GB", size.toFloat() / (1024 * 1024 * 1024)) size >= 1024 * 1024 -> String.format("%.2f MB", size.toFloat() / (1024 * 1024)) size >= 1024 -> String.format("%.2f KB", size.toFloat() / 1024) else -> "$size B" } }
5. 收尾处理
- 当所有任务完成或取消时,记得注销
ContentObserver,避免内存泄漏。 - 如果App在后台运行,可能会被系统限制监听能力,这时可以考虑将监听逻辑放到前台服务中(刚好我们的自定义通知可以作为前台服务的通知,符合Android的后台限制要求)。
注意事项
- 权限配置:确保申请了
INTERNET权限;如果下载到外部存储,Android 10及以上需要WRITE_EXTERNAL_STORAGE权限(或使用MediaStore进行文件存储)。 - 任务ID管理:务必保存自己发起的所有下载任务ID,避免查询到其他应用的下载任务,导致进度计算错误。
- 异常处理:要处理下载失败的任务(比如重新发起下载,或者在通知中提示用户),避免整体进度卡住。
内容的提问来源于stack exchange,提问作者csbako
相关产品推荐
相关产品推荐

