You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 21:27:18