如何将指定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
相关产品推荐
相关产品推荐

