Android如何使用默认DownloadManager将文件下载到指定目录
实现方案
系统DownloadManager是独立运行的系统服务进程,和应用本身进程权限隔离,默认没有权限直接写入应用私有内部存储路径(也就是你要存的/data/data/com.music.video/files/目录),直接配置目标路径会触发权限错误导致下载失败。兼容性最高的实现方式是临时下载到应用有权限让DownloadManager写入的目录,下载完成后自动移动到目标路径,具体Kotlin实现步骤如下:
步骤1:提前创建目标存储目录
不要硬编码内部存储根路径,用系统API获取避免多用户、应用双开场景路径异常:
// 替换成你自己的m4a文件实际下载地址 val downloadUrl = "你的文件下载链接" // 该路径等价于 /data/data/com.music.video/files/ val targetDir = File(filesDir, "mu/1/") if (!targetDir.exists()) { targetDir.mkdirs() } val targetFile = File(targetDir, "2.m4a")
步骤2:配置DownloadManager下载请求
临时文件存到应用外部私有目录,这个目录DownloadManager默认有写入权限,不需要申请额外存储权限,全Android版本兼容:
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager val downloadRequest = DownloadManager.Request(Uri.parse(downloadUrl)) .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE) // 根据实际需求调整下载通知显示规则 .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) // 临时文件存到外部私有下载目录,不会出现在用户公共文件列表 .setDestinationInExternalFilesDir( this, Environment.DIRECTORY_DOWNLOADS, "temp_${System.currentTimeMillis()}.m4a" ) val downloadId = downloadManager.enqueue(downloadRequest)
步骤3:监听下载完成状态,移动文件到目标路径
注册下载完成广播,下载成功后将临时文件拷贝到目标内部路径,最后清理临时文件:
private val downloadReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { val completedId = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) if (completedId == downloadId) { val query = DownloadManager.Query().setFilterById(downloadId) val cursor = downloadManager.query(query) if (cursor.moveToFirst()) { val downloadStatus = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL) { val tempUri = Uri.parse( cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI)) ) // 拷贝临时文件到目标内部路径 contentResolver.openInputStream(tempUri)?.use { inputStream -> targetFile.outputStream().use { outputStream -> inputStream.copyTo(outputStream) } } // 删除临时文件、注销广播避免内存泄漏 downloadManager.remove(downloadId) unregisterReceiver(this) } } cursor.close() } } } // 动态注册下载完成广播 registerReceiver( downloadReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) )
注意事项
- 不要尝试直接给
DownloadManager.Request设置内部存储的file://类型目标地址,系统服务进程无应用私有目录写入权限,会直接下载失败 - 上述实现从Android 6.0到Android 14全版本兼容,不需要申请任何存储权限
- 如果你需要临时文件在下载过程中也不对外暴露,可以把临时存储路径换成
context.getCacheDir()对应的FileProvider授权uri,但部分厂商定制ROM会对跨进程文件授权做限制,兼容性不如上面的方案
内容的提问来源于stack exchange,提问作者Prashant Gosai
相关产品推荐
相关产品推荐

