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

求助:将ACTION_OPEN_DOCUMENT选中的PDF保存并合并的正确方法

问题分析
  • 核心错误是直接用uri?.path构建FileInputStream:content://格式的Uri并非本地文件路径,无法通过File类直接访问,必须通过ContentResolver的openInputStream()方法获取文件流。
  • 存储路径问题:Android 10(API 29)及以上版本,直接操作Environment.getExternalStoragePublicDirectory()会触发分区存储限制,需改用合规的文件写入方式。
正确实现步骤

1. 正确获取选中PDF的输入流

使用Jetpack规范的ActivityResultContracts.OpenDocument选择PDF,通过ContentResolver获取文件流:

// 注册选择PDF的启动器
private val pickPdfLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
    uri ?: return@registerForActivityResult

    requireContext().contentResolver.openInputStream(uri)?.use { inputStream ->
        // 将选中PDF的流传入合并逻辑,或先保存到应用可访问目录
        savePdfToAppDir(inputStream, "selected_pdf.pdf")
        // 执行PDF合并
        mergePdfs("selected_pdf.pdf", "generated_pdf.pdf", "merged_result.pdf")
    } ?: run {
        println("无法获取选中PDF的文件流")
    }
}

// 触发PDF选择操作
fun startPickPdf() {
    pickPdfLauncher.launch(arrayOf("application/pdf"))
}

2. 合规保存文件(若合并需要本地文件)

将选中的PDF保存到应用专属外部目录(无需额外权限):

private fun savePdfToAppDir(inputStream: InputStream, fileName: String) {
    val context = requireContext()
    // 应用专属的外部文档目录,Android 10+无需WRITE_EXTERNAL_STORAGE权限
    val targetFile = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), fileName)

    BufferedOutputStream(FileOutputStream(targetFile)).use { outputStream ->
        inputStream.copyTo(outputStream, 1024)
    }
}

3. PDF合并实现(以iText 7为例)

先添加iText依赖到build.gradle:

implementation 'com.itextpdf:itext7-core:7.2.5'

编写合并逻辑:

private fun mergePdfs(selectedPdfName: String, generatedPdfName: String, outputName: String) {
    val context = requireContext()
    val selectedPdf = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), selectedPdfName)
    val generatedPdf = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), generatedPdfName)
    val outputPdf = File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), outputName)

    PdfWriter(outputPdf).use { writer ->
        PdfDocument(writer).use { outputDoc ->
            // 合并选中的PDF
            PdfDocument(PdfReader(selectedPdf)).use { inputDoc1 ->
                inputDoc1.copyPagesTo(1, inputDoc1.numberOfPages, outputDoc)
            }
            // 合并程序生成的PDF
            PdfDocument(PdfReader(generatedPdf)).use { inputDoc2 ->
                inputDoc2.copyPagesTo(1, inputDoc2.numberOfPages, outputDoc)
            }
        }
    }

    // 可选:将合并结果复制到公共下载目录(Android 10+兼容)
    copyToPublicDownloads(outputPdf)
}

// 复制到公共下载目录的兼容实现
private fun copyToPublicDownloads(sourceFile: File) {
    val context = requireContext()
    val contentValues = ContentValues().apply {
        put(MediaStore.Downloads.DISPLAY_NAME, "merged_result.pdf")
        put(MediaStore.Downloads.MIME_TYPE, "application/pdf")
        put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
    }

    context.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)?.let { uri ->
        context.contentResolver.openOutputStream(uri)?.use { outputStream ->
            FileInputStream(sourceFile).use { inputStream ->
                inputStream.copyTo(outputStream)
            }
        }
    }
}
关键注意事项
  • 权限处理:Android 10+访问应用专属目录无需WRITE_EXTERNAL_STORAGE权限;写入公共下载目录时,Android 10-11需该权限,Android 12+需MANAGE_EXTERNAL_STORAGE权限(或使用上述MediaStore方式写入,无需特殊权限)。
  • 流的管理:始终用use块自动关闭流,避免资源泄漏。
  • PDF库替代:除iText外,也可使用Apache PDFBox for Android,注意适配Android环境的依赖版本。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 16:24:59