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

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时内存分配失败。

原因

  1. 尽管开启了MemorySavingMode,但默认的PdfReader可能将部分文件内容加载到内存,大PDF的高分辨率图片等资源在复制时会占用大量内存;
  2. 原代码中getNextPdfWriter里循环flush所有pdfWritersList中的writer,导致多个文档的内容同时驻留内存,加剧内存压力;
  3. Android应用默认内存限制较小,无法处理超大PDF的资源复制。

解决办法

  1. 优化PdfReader的文件读取方式:使用RandomAccessFileOrArray直接读取文件,避免内存加载整个文件;
  2. 移除不必要的批量flush:只flush当前创建的writer,不要循环flush所有历史writer;
  3. 开启Android大内存堆:在AndroidManifest.xml的<application>标签添加android:largeHeap="true",提升应用内存上限;
  4. 优化资源复制逻辑:确保流式处理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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 00:50:28