iText7 Android中splitBySize异常行为与OOM问题求助
iText7 Android PDF拆分问题解决方案
问题背景
基于André Lemos的建议使用iText7 Android分支,采用以下Kotlin代码按指定字节大小拆分PDF:
suspend fun splitPDF(src: File, byteSize: Long) { yield() val readerPdfDocument = PdfDocument(PdfReader(src).setMemorySavingMode(true)) val splitDocuments: List<PdfDocument> = object : PdfSplitter(readerPdfDocument) { var partNumber = 1 override fun getNextPdfWriter(documentPageRange: PageRange?): PdfWriter { return try { val splitTempFile: File = File.createTempFile("splitTempFile_" + partNumber++ + "_", ".pdf") splitTempFilesList.add(splitTempFile) val pdfWriter = PdfWriter(splitTempFile) pdfWriter.setSmartMode(true) pdfWriter.compressionLevel = 9 pdfWritersList.add(pdfWriter) pdfWritersList.forEach { pdfWriter -> pdfWriter.flush() } pdfWriter } catch (e: FileNotFoundException) { throw RuntimeException(e) } } }.splitBySize(byteSize) for (doc in splitDocuments) { doc.close() } readerPdfDocument.close() }
遇到两个异常问题:
案例1:小PDF无法按阈值合并多页拆分
108KB的PDF设置拆分大小为108000字节(接近原文件大小)时,被拆分为多个单页文件;设置为111000字节(略大于原文件)时才生成单个PDF。
原因
PdfSplitter.splitBySize()是基于单页的预估大小判断是否添加下一页,但实际生成的PDF包含文档元数据、字体、共享资源等额外内容,单页拆分后的文件占比远高于原文件中对应单页的占比。对于小文件,这些共享资源的占比极高,导致每添加一页就会超过设置的大小阈值,最终只能拆成单页。
解决办法
放弃splitBySize()的自动拆分,改为手动控制页面合并逻辑:逐页添加到目标文档,每次添加后检查临时文件的实际大小,超过阈值则新建文档继续添加。示例修改代码如下:
suspend fun splitPDFManual(src: File, maxSize: Long) { yield() val reader = PdfReader(RandomAccessFileOrArray(src.absolutePath)).setMemorySavingMode(true) val sourceDoc = PdfDocument(reader) var currentDoc: PdfDocument? = null var currentWriter: PdfWriter? = null var partNumber = 1 val totalPages = sourceDoc.numberOfPages for (pageNum in 1..totalPages) { yield() if (currentDoc == null) { val tempFile = File.createTempFile("split_$partNumber_", ".pdf") splitTempFilesList.add(tempFile) currentWriter = PdfWriter(tempFile).apply { setSmartMode(true) compressionLevel = 9 } currentDoc = PdfDocument(currentWriter) pdfWritersList.add(currentWriter) } // 复制当前页到目标文档 sourceDoc.copyPagesTo(pageNum, pageNum, currentDoc) // 强制刷新到文件,才能获取准确大小 currentWriter.flush() currentDoc.flush() // 检查当前文件大小是否超过阈值(未到最后一页时才拆分) if (currentDoc.file.size() > maxSize && pageNum < totalPages) { currentDoc.close() currentWriter.close() currentDoc = null currentWriter = null partNumber++ } } // 关闭最后一个文档 currentDoc?.close() currentWriter?.close() sourceDoc.close() }
案例3:大PDF拆分触发OutOfMemoryError
232MB的PDF设置拆分大小为12MB时,立即触发内存不足错误,栈信息显示在复制PdfStream时内存分配失败。
原因
- 尽管开启了
MemorySavingMode,但默认的PdfReader可能将部分文件内容加载到内存,大PDF的高分辨率图片等资源在复制时会占用大量内存; - 原代码中
getNextPdfWriter里循环flush所有pdfWritersList中的writer,导致多个文档的内容同时驻留内存,加剧内存压力; - Android应用默认内存限制较小,无法处理超大PDF的资源复制。
解决办法
- 优化PdfReader的文件读取方式:使用
RandomAccessFileOrArray直接读取文件,避免内存加载整个文件; - 移除不必要的批量flush:只flush当前创建的writer,不要循环flush所有历史writer;
- 开启Android大内存堆:在
AndroidManifest.xml的<application>标签添加android:largeHeap="true",提升应用内存上限; - 优化资源复制逻辑:确保流式处理PDF资源,避免一次性加载大对象到内存。
修改后的核心代码片段:
// 优化PdfReader初始化 val reader = PdfReader(RandomAccessFileOrArray(src.absolutePath)).setMemorySavingMode(true) val readerPdfDocument = PdfDocument(reader) // 修改getNextPdfWriter中的flush逻辑 override fun getNextPdfWriter(documentPageRange: PageRange?): PdfWriter { return try { val splitTempFile: File = File.createTempFile("splitTempFile_" + partNumber++ + "_", ".pdf") splitTempFilesList.add(splitTempFile) val pdfWriter = PdfWriter(splitTempFile) pdfWriter.setSmartMode(true) pdfWriter.compressionLevel = 9 pdfWritersList.add(pdfWriter) // 只flush当前writer,不要循环flush所有 pdfWriter.flush() pdfWriter } catch (e: FileNotFoundException) { throw RuntimeException(e) } }
内容的提问来源于stack exchange,提问作者Deepanshu
相关产品推荐
相关产品推荐

