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

如何在Scoped Storage中保存PDF?Android存储适配问题求助

解决Retrofit下载PDF无法写入内部存储的问题

我懂你现在的困扰:切换到Retrofit下载PDF后,明明请求响应成功了,可就是没法把数据流写入内部存储的文件里。结合你的代码,我整理了几个关键的排查点和修复方案:

1. 先确保目标目录已创建

你的代码直接创建了文件对象,但如果DESTINATION_DIRECTORY对应的文件夹不存在,写入操作会静默失败。在创建文件之前,一定要先检查并创建父目录:

val destDir = File(context.filesDir, DESTINATION_DIRECTORY)
if (!destDir.exists()) {
    destDir.mkdirs() // 递归创建所有层级的目录
}
file = File(destDir, "$FILE_NAME$FILE_EXTENSION")

2. 给数据流拷贝加上异常处理

当前的copyInputStreamToFile没有捕获IO异常,一旦出现读写错误你完全不知道问题出在哪。修改函数,加上异常捕获和详细日志:

private fun File.copyInputStreamToFile(inputStream: InputStream) {
    this.outputStream().use { fileOut ->
        try {
            Log.i("new","开始写入文件:${this.path}")
            inputStream.copyTo(fileOut, bufferSize = 8192) // 指定缓冲区大小提升写入效率
            fileOut.flush() // 强制把缓存数据写入磁盘
            Log.i("new","文件写入完成")
        } catch (e: IOException) {
            Log.e("new", "写入文件失败", e)
            throw e // 可以选择抛出异常让上层处理,也可以根据业务需求做降级处理
        }
    }
}

3. 妥善处理Retrofit的响应数据流

Retrofit返回的数据流需要确保在整个拷贝过程中保持打开状态,建议把inputStream也放进use块里,避免资源泄漏:

response.body()?.byteStream()?.use { inputStream ->
    file!!.copyInputStreamToFile(inputStream)
}

4. 验证文件写入后的状态

在写入完成后,添加日志检查文件大小是否合理:

Log.i("new", "写入后文件大小:${file?.length() ?: 0} bytes")

如果文件大小为0,说明数据流根本没写入;如果远小于预期,可能是数据流读取不完整。

后续PDFRenderer渲染的注意事项

内部存储的文件是应用私有,PDFRenderer可以直接读取,但要注意:

  • 确保文件完整(可以对比服务器端文件大小)
  • 打开文件时用ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
  • 渲染完成后要关闭PDFRenderer和ParcelFileDescriptor,避免资源泄漏

修改后的完整readAndDownloadFile函数示例:

fun readAndDownloadFile(context: Context) {
    readQuraanInterface?.downloadFile()
    Coroutines.io {
        val destDir = File(context.filesDir, DESTINATION_DIRECTORY)
        if (!destDir.exists()) {
            destDir.mkdirs()
        }
        file = File(destDir, "$FILE_NAME$FILE_EXTENSION")
        if (file?.exists() == true) {
            Log.i("new", "文件已存在,直接渲染")
            renderPDF()
            showPdf(mPageIndex, Direction.None)
        } else {
            Log.i("new","开始下载PDF")
            val response = readQuraanRepository.downloadPdf()
            if (response.isSuccessful) {
                Log.i("new","下载响应成功,准备写入文件:${file!!.path}")
                response.body()?.byteStream()?.use { inputStream ->
                    file!!.copyInputStreamToFile(inputStream)
                }
                Log.i("new","文件写入完成,大小:${file?.length() ?: 0} bytes")
                renderPDF()
                showPdf(mPageIndex, Direction.None)
            } else {
                Log.i("new","下载失败,响应码:${response.code()}")
                Coroutines.main {
                    response.errorBody()?.string()?.let {
                        readQuraanInterface?.downloadFailed(it)
                    }
                }
            }
        }
    }
}

你可以先按这些步骤排查,重点看日志里的错误信息,应该能很快定位到写入失败的原因。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 16:47:38