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

Android中使用DownloadManager无法下载PDF的问题排查

问题排查与解决方案

一、现有代码的潜在问题

  1. 明文流量拦截(Android 9+)
    你使用的是HTTP协议URL,Android 9(API 28)及以上系统默认禁止明文网络请求,会直接拦截下载请求,且可能无明显Logcat报错。

  2. 文件路径格式错误
    setDestinationInExternalFilesDir的第三个参数无需添加File.separator——Environment.DIRECTORY_DOWNLOADS本身已是标准路径,额外的分隔符会导致路径异常,系统无法正确创建目标文件。

  3. 权限配置缺失

    • 必须在AndroidManifest.xml中声明网络权限:<uses-permission android:name="android.permission.INTERNET" />
    • Android 9及以下版本还需添加存储写入权限:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
  4. 无下载状态反馈
    仅调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 07:16:06