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

如何将指定URL音频下载至externalCacheDir实现离线访问,有无优于DownloadManager的方案?

现有代码的可优化点
  • setDestinationInExternalPublicDir 方法的第一个参数要求传入系统公共目录常量(比如Environment.DIRECTORY_DOWNLOADS),直接传入externalCacheDir路径会触发报错,该方法不支持直接指定应用私有缓存目录
  • 函数声明为无返回值的fun downloadAudio(url:String),末尾return fileName存在语法错误
  • 没有实现文件映射逻辑,下载完成后无法关联原始URL和本地文件,不方便后续无网络场景调用
  • 原生DownloadManager的自定义能力有限,无法灵活配置重试策略、缓存过期规则、断点续传逻辑
更优实现方案

方案1:OkHttp 轻量自定义实现(推荐,适配绝大多数场景)

优势是无需依赖复杂库,逻辑可控,可直接写入应用私有缓存目录,自动去重避免重复下载,适配所有Android版本无兼容问题。
依赖配置:

implementation "com.squareup.okhttp3:okhttp:4.12.0"

实现代码:

// 协程实现,返回本地音频路径,下载失败返回null
suspend fun downloadAudio(url: String): String? = withContext(Dispatchers.IO) {
    val context = getApplication<Application>()
    val cacheDir = context.externalCacheDir ?: return@withContext null
    // 用URL哈希作为文件名,相同音频自动去重,不用重复下载
    val fileName = "${url.hashCode()}.mp3"
    val targetFile = File(cacheDir, fileName)

    // 已有有效缓存直接返回
    if (targetFile.exists() && targetFile.length() > 0) {
        return@withContext targetFile.absolutePath
    }

    return@withContext try {
        val request = okhttp3.Request.Builder().url(url).build()
        val response = OkHttpClient.Builder()
            .connectTimeout(15, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build()
            .newCall(request)
            .execute()
        if (!response.isSuccessful) return@withContext null

        response.body?.byteStream()?.use { inputStream ->
            FileOutputStream(targetFile).use { outputStream ->
                inputStream.copyTo(outputStream)
            }
        }
        targetFile.absolutePath
    } catch (e: Exception) {
        e.printStackTrace()
        // 下载失败清理残留文件
        if (targetFile.exists()) targetFile.delete()
        null
    }
}

方案2:修改原生DownloadManager实现(适合需要系统级下载调度的场景)

如果你需要系统后台保活下载、大文件稳定下载的能力,可以修改原有代码适配私有缓存目录:

fun downloadAudio(url: String): Long {
    val request = DownloadManager.Request(url.toUri())
    val externalCacheDir = getApplication<Application>().externalCacheDir!!
    val fileName = "${UUID.randomUUID()}.mp3"
    val targetFile = File(externalCacheDir, fileName)
    // 改用setDestinationUri指定私有缓存目录路径
    request.setDestinationUri(Uri.fromFile(targetFile))

    // 不需要通知的话可以设为VISIBILITY_HIDDEN,需要申请android.permission.DOWNLOAD_WITHOUT_NOTIFICATION权限
    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
    request.setDescription("音频下载中")
    request.setTitle(fileName)

    val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
    // 返回下载ID,可用于后续查询下载状态、接收下载完成广播
    return downloadManager.enqueue(request)
}
方案选择建议
  • 少量音频下载、不需要复杂下载能力的场景,优先选OkHttp自定义实现,逻辑简单无兼容问题,可灵活扩展进度回调、断点续传等能力
  • 大文件下载、需要后台稳定保活下载的场景,选择修改后的DownloadManager方案

内容的提问来源于stack exchange,提问作者Omar Khaled

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 08:06:03