PdfRenderer.openPage抛出未预期IllegalStateException问题求助
问题描述
调用PdfRenderer.openPage时意外抛出java.lang.IllegalStateException。根据官方文档,仅当先调用PdfRenderer.close()后再打开页面才会触发该异常,但调试确认调用openPage前渲染器未被关闭。
仅在短时间内快速切换20-30页时才会触发该异常(95%场景下应用正常)。起初怀疑内存问题,但已释放旧页面,且性能分析显示内存峰值未达1GB,可排除该因素。
完整堆栈信息
FATAL EXCEPTION: pool-3-thread-2 Process: com.example.my_app, PID: 11178 java.lang.IllegalStateException: cannot load page at android.graphics.pdf.PdfRenderer.nativeOpenPageAndGetSize(Native Method) at android.graphics.pdf.PdfRenderer.-$$Nest$smnativeOpenPageAndGetSize(Unknown Source:0) at android.graphics.pdf.PdfRenderer$Page.<init>(PdfRenderer.java:315) at android.graphics.pdf.PdfRenderer$Page.<init>(Unknown Source:0) at android.graphics.pdf.PdfRenderer.openPage(PdfRenderer.java:233) at com.example.my_app.LoadedDocument.renderPage(LoadedDocument.kt:353)
相关代码
private fun renderAndSavePage(pageNumber: Int) { pagesSemaphore.acquire() // Exit early if a previous call rendered this page. if (pages[pageNumber].width != EMPTY_BITMAP_WIDTH) { pagesSemaphore.release() return } // If, in the time between this function call and acquiring the semaphore, // the user has quickly switched pages beyond where this needs to be loaded, // skip loading this. if (abs(pageNumber - metadata.pageNo) > PAGE_BUFFER) { pagesSemaphore.release() return } // Open the page and save it to a bitmap. val page = renderer.openPage(pageNumber) val ratio = page.height.toFloat() / page.width.toFloat() val bitmap = Bitmap.createBitmap( imagePixelWidth, (imagePixelWidth * ratio).toInt(), Bitmap.Config.ARGB_8888 ) bitmap.eraseColor(Color.WHITE) page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) pages[pageNumber] = bitmap page.close() pagesSemaphore.release() }
问题分析与解决办法
触发原因
PdfRenderer线程安全问题:PdfRenderer本身并非线程安全组件,多线程同时调用openPage会导致底层Native资源状态混乱,即使代码中调用了page.close(),快速切换时的并发操作仍可能让资源释放不及时,触发异常。- 信号量控制有局限:当前的
pagesSemaphore仅限制了渲染任务的并发数,但未约束PdfRenderer的访问线程,无法避免多线程对同一渲染器的竞争。 - 资源释放延迟:
page.close()的Native层资源回收存在延迟,快速触发的openPage调用可能在旧页面资源未完全释放时执行,导致底层资源冲突。
解决办法
- 强制
PdfRenderer单线程访问:将所有PDF渲染相关操作(openPage、render、close)放到同一个线程执行,比如用HandlerThread创建专用渲染线程,所有任务都提交到该线程队列处理,彻底避免多线程竞争。 - 添加异常捕获与重试逻辑:在
openPage调用处捕获IllegalStateException,进行1-2次有限重试,重试前短暂休眠等待资源释放:val page = try { renderer.openPage(pageNumber) } catch (e: IllegalStateException) { Thread.sleep(50) renderer.openPage(pageNumber) // 仅重试一次,避免阻塞 } - 优化预加载与任务取消逻辑:调整
PAGE_BUFFER大小减少并发渲染任务数;用协程Job或任务队列管理渲染请求,当用户快速切换页面时,取消掉已提交但未执行的非必要渲染任务,减少无效资源占用。 - 增加渲染器状态校验:调用
openPage前检查renderer是否意外关闭,若已关闭则重新初始化:if (renderer.isClosed) { // 重新创建PdfRenderer实例,需确保FileDescriptor有效 renderer = PdfRenderer(...) }
内容的提问来源于stack exchange,提问作者Calvin Godfrey
相关产品推荐
相关产品推荐

