Android中使用DownloadManager无法下载PDF的问题排查
问题排查与解决方案
一、现有代码的潜在问题
明文流量拦截(Android 9+)
你使用的是HTTP协议URL,Android 9(API 28)及以上系统默认禁止明文网络请求,会直接拦截下载请求,且可能无明显Logcat报错。文件路径格式错误
setDestinationInExternalFilesDir的第三个参数无需添加File.separator——Environment.DIRECTORY_DOWNLOADS本身已是标准路径,额外的分隔符会导致路径异常,系统无法正确创建目标文件。权限配置缺失
- 必须在
AndroidManifest.xml中声明网络权限:<uses-permission android:name="android.permission.INTERNET" /> - Android 9及以下版本还需添加存储写入权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
- 必须在
无下载状态反馈
仅调用enqueue后未监听下载状态,无法确认请求是否成功入队、下载过程是否出现隐性错误。
二、代码改进建议
1. 修复Manifest配置
<!-- 添加权限 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <!-- 允许明文流量(针对HTTP URL) --> <application ... android:usesCleartextTraffic="true"> ... </application>
2. 修正文件路径
// 移除多余的File.separator .setDestinationInExternalFilesDir( context, Environment.DIRECTORY_DOWNLOADS, "my.pdf" )
3. 添加下载状态查询
// 在AndroidDownloader中新增状态查询方法 fun getDownloadStatus(downloadId: Long): Int { downloadManager ?: return DownloadManager.STATUS_FAILED val query = DownloadManager.Query().setFilterById(downloadId) downloadManager.query(query).use { cursor -> if (cursor.moveToFirst()) { return cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) } } return DownloadManager.STATUS_FAILED } // 在UserFragment中调用后验证状态 private fun downloadPdf(pdfUrl: String) { val downloader = AndroidDownloader(requireContext()) val downloadId = downloader.downloadFile(pdfUrl) // 延迟查询(生产环境建议用ContentObserver监听) Handler(Looper.getMainLooper()).postDelayed({ when(downloader.getDownloadStatus(downloadId)) { DownloadManager.STATUS_SUCCESSFUL -> Toast.makeText(requireContext(), "下载完成", Toast.LENGTH_SHORT).show() DownloadManager.STATUS_FAILED -> Toast.makeText(requireContext(), "下载失败", Toast.LENGTH_SHORT).show() else -> Toast.makeText(requireContext(), "下载中", Toast.LENGTH_SHORT).show() } }, 2000) }
4. 完善初始化校验
// 对DownloadManager初始化做非空校验 private val downloadManager: DownloadManager? = context.getSystemService(DownloadManager::class.java) override fun downloadFile(url: String): Long { downloadManager ?: return -1 // 初始化失败返回-1标记 // ... 原有请求构建逻辑 }
三、Kotlin中更优的PDF下载方式
使用OkHttp + Kotlin协程实现自定义下载,比系统DownloadManager更灵活,支持进度监听、异常捕获、自定义逻辑扩展:
示例代码
// 先添加OkHttp依赖到build.gradle // implementation("com.squareup.okhttp3:okhttp:4.12.0") class CoroutineDownloader(private val context: Context) { private val okHttpClient = OkHttpClient() suspend fun downloadPdf(url: String, fileName: String): Boolean { return withContext(Dispatchers.IO) { val request = Request.Builder().url(url).build() try { okHttpClient.newCall(request).execute().use { response -> if (!response.isSuccessful) return@withContext false val targetFile = File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), fileName) response.body?.byteStream()?.use { input -> targetFile.outputStream().use { output -> input.copyTo(output) } } return@withContext true } } catch (e: Exception) { e.printStackTrace() return@withContext false } } } } // 在Fragment中调用 private fun downloadPdfWithCoroutine(url: String) { lifecycleScope.launch { val downloader = CoroutineDownloader(requireContext()) val success = downloader.downloadPdf(url, "my.pdf") Toast.makeText(requireContext(), if (success) "下载完成" else "下载失败", Toast.LENGTH_SHORT).show() } }
该方案优势:
- 可实时监听下载进度(通过读取字节流计算)
- 直接捕获异常,便于调试
- 支持断点续传、文件预校验等自定义扩展
内容的提问来源于stack exchange,提问作者Navi_Here
相关产品推荐
相关产品推荐

