Android:如何将PDF下载到选定目录?文件未保存至目标路径问题
问题分析与解决方案
问题根源
你使用setDestinationInExternalPublicDir()的方式完全错误。这个方法的第一个参数是系统公共目录的类型常量(比如Environment.DIRECTORY_DOWNLOADS),而非通过SAF获取的目录Uri字符串。这就是文件没有保存到选定目录的核心原因——DownloadManager把你传入的Uri字符串当成了公共目录的子路径,最终文件被存到了默认的公共下载目录下。
解决方案
DownloadManager本身不支持直接将文件写入通过SAF(OpenDocumentTree)获取的自定义目录,你可以采用以下两种方案之一:
方案1:先下载到临时目录,再复制到选定目录
- 用DownloadManager将文件下载到应用临时目录或公共下载目录
- 监听下载完成事件,再通过SAF将文件复制到用户选定的目录
代码示例:
// 第一步:下载到应用临时缓存目录 fun downloadPdfToTemp(context: Context, pdfUrl: String, pdfTitle: String, directoryUri: Uri?) { val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager val tempFile = File(context.externalCacheDir, pdfTitle) val request = DownloadManager.Request(Uri.parse(pdfUrl)) .setTitle(pdfTitle) .setMimeType("application/pdf") .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) .setDestinationUri(Uri.fromFile(tempFile)) val downloadId = downloadManager.enqueue(request) // 注册广播接收器监听下载完成 val downloadCompleteReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { val receivedId = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) if (receivedId == downloadId && directoryUri != null) { // 下载完成,执行文件复制 copyToSelectedDirectory(context, tempFile, pdfTitle, directoryUri) context?.unregisterReceiver(this) } } } context.registerReceiver(downloadCompleteReceiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) } // 第二步:将临时文件复制到SAF选定目录 private fun copyToSelectedDirectory(context: Context?, sourceFile: File, fileName: String, targetDirUri: Uri) { context ?: return val targetDir = DocumentFile.fromTreeUri(context, targetDirUri) ?: return val targetFile = targetDir.createFile("application/pdf", fileName) ?: return // 执行文件复制 context.contentResolver.openOutputStream(targetFile.uri)?.use { outputStream -> sourceFile.inputStream().use { inputStream -> inputStream.copyTo(outputStream) } } // 删除临时文件 sourceFile.delete() }
方案2:直接通过SAF写入自定义目录(不使用DownloadManager)
如果不需要DownloadManager的系统级通知和下载管理能力,可以直接用网络库(如OkHttp)下载文件并写入SAF目录:
import okhttp3.OkHttpClient import okhttp3.Request import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext // 协程中执行下载,避免阻塞主线程 suspend fun downloadPdfDirectlyToSelectedDir(context: Context, pdfUrl: String, fileName: String, targetDirUri: Uri) = withContext(Dispatchers.IO) { val client = OkHttpClient() val request = Request.Builder().url(pdfUrl).build() val response = client.newCall(request).execute() if (!response.isSuccessful) throw IOException("下载失败: ${response.code}") val targetDir = DocumentFile.fromTreeUri(context, targetDirUri) ?: return@withContext val targetFile = targetDir.createFile("application/pdf", fileName) ?: return@withContext // 写入文件到选定目录 context.contentResolver.openOutputStream(targetFile.uri)?.use { outputStream -> response.body?.byteStream()?.use { inputStream -> inputStream.copyTo(outputStream) } } }
注意事项
- 使用SAF时,
OpenDocumentTree会自动请求目录写入权限,但需要用户确认授权 - 方案1中,务必在下载完成后注销广播接收器,避免内存泄漏
- 方案2需要添加OkHttp依赖到
build.gradle(Module级别):implementation("com.squareup.okhttp3:okhttp:4.12.0")
内容的提问来源于stack exchange,提问作者Nirvik
相关产品推荐
相关产品推荐

