Android中如何为DownloadManager添加ProgressBar与完成监听
实现DownloadManager下载进度监听与完成状态更新
核心思路
DownloadManager没有直接的进度回调接口,需要通过ContentObserver监听下载任务的状态变化,定期查询下载进度来更新ProgressBar;同时通过查询下载状态判断任务是否完成,进而更新UI状态。
实现步骤
1. 保存下载任务ID
调用dm.enqueue(request)会返回唯一的下载ID,需保存该ID追踪对应下载任务:
// 类成员变量,保存当前下载任务ID private var currentDownloadId: Long = -1L // 类成员变量,保存ContentObserver用于后续注销 private var downloadObserver: ContentObserver? = null private fun downloadImageNew(filename: String, downloadUrlOfImage: String) { try { val dm = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager? val downloadUri = Uri.parse(downloadUrlOfImage) val request = DownloadManager.Request(downloadUri) request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE) .setTitle(filename) .setMimeType("image/jpeg") // 关闭系统默认通知,避免和自定义ProgressBar重复 .setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) .setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, File.separator + filename + ".jpg") // 保存下载ID并启动任务 currentDownloadId = dm!!.enqueue(request) Toast.makeText(requireContext(), "图片下载已启动", Toast.LENGTH_SHORT).show() // 注册监听进度的ContentObserver registerDownloadObserver() } catch (e: Exception) { Toast.makeText(requireContext(), "图片下载失败", Toast.LENGTH_SHORT).show() } }
2. 注册ContentObserver监听进度
自定义ContentObserver监听DownloadManager的任务状态变化:
private fun registerDownloadObserver() { downloadObserver = object : ContentObserver(Handler(Looper.getMainLooper())) { override fun onChange(selfChange: Boolean) { super.onChange(selfChange) queryDownloadStatus() } } // 注册监听DownloadManager的查询URI requireContext().contentResolver.registerContentObserver( DownloadManager.Query.EXTERNAL_CONTENT_URI, true, downloadObserver!! ) }
3. 查询进度并更新UI
通过DownloadManager的query方法获取任务详情,计算进度并处理完成/失败状态:
private fun queryDownloadStatus() { val dm = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager? val query = DownloadManager.Query().setFilterById(currentDownloadId) dm?.query(query)?.use { cursor -> if (cursor.moveToFirst()) { // 获取下载状态、已下载大小、总大小 val status = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) val downloadedBytes = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) val totalBytes = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) // 更新ProgressBar(假设布局中id为progress_bar) if (totalBytes > 0) { val progress = (downloadedBytes * 100 / totalBytes).toInt() binding.progressBar.progress = progress } // 处理不同状态 when (status) { DownloadManager.STATUS_SUCCESSFUL -> { // 下载完成:隐藏进度条,更新状态文本 binding.progressBar.visibility = View.GONE binding.downloadStatusTv.text = "下载完成" Toast.makeText(requireContext(), "图片已保存至图库", Toast.LENGTH_SHORT).show() // 注销Observer避免内存泄漏 downloadObserver?.let { requireContext().contentResolver.unregisterContentObserver(it) downloadObserver = null } } DownloadManager.STATUS_FAILED -> { // 下载失败:更新状态,注销Observer binding.progressBar.visibility = View.GONE binding.downloadStatusTv.text = "下载失败" Toast.makeText(requireContext(), "图片下载失败,请重试", Toast.LENGTH_SHORT).show() downloadObserver?.let { requireContext().contentResolver.unregisterContentObserver(it) downloadObserver = null } } DownloadManager.STATUS_RUNNING -> { // 下载中:显示进度条,更新状态文本 binding.progressBar.visibility = View.VISIBLE binding.downloadStatusTv.text = "下载中..." } // 可按需处理暂停、等待等其他状态 } } } }
4. 生命周期中注销Observer
在Fragment/Activity的onDestroy方法中注销Observer,防止内存泄漏:
override fun onDestroy() { super.onDestroy() downloadObserver?.let { requireContext().contentResolver.unregisterContentObserver(it) downloadObserver = null } }
注意事项
- 确保布局中存在
ProgressBar和状态文本控件,根据实际ID修改代码中的控件引用。 - Android 10及以上版本需适配Scoped Storage,若使用
setDestinationInExternalPublicDir,需申请READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限。 - 多下载任务场景需维护下载ID与对应UI控件的映射,避免进度混淆。
内容的提问来源于stack exchange,提问作者Jojo Drink
相关产品推荐
相关产品推荐

