求助:将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
相关产品推荐
相关产品推荐

