androidkotlinexceptionpdfrenderer

PdfRenderer.openPage is unexpectedy throwing a java.lang.IllegalStateException


According to the documentation (https://developer.android.com/reference/android/graphics/pdf/PdfRenderer#openPage(int)), IllegalStateException should only be thrown if PdfRenderer.close() is called before attempting to open a page, but I've stepped through in a debugger and confirmed that the renderer has not been closed before the call to openPage. Here's the full stack trace:

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)

95% of the time, my app works fine; the exception is only thrown if I tap on the screen to rapidly go through 20-30 pages in a short period of time. At first, I thought I might have been using too much memory, but I deallocate previously loaded page, and as the below profiler shows, I'm not even hitting 1GB of memory usage, so I don't believe it's that.

Profiler showing a peak memory usage under 1GB

Here's how I'm using the method:

    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()
    }

Solution

  • At first I thought it was happening randomly since it was triggering on multiple different pages on the pdf. But after opening the pdf in a different reader, it turns out that the pdf I was testing with happened to have multiple corrupted pages in it, and they exactly lined up with the ones that were causing the exception to get thrown in my app.

    I'm not familiar enough the PDF specification to try to diagnose exactly how it is misformed, but at least I know that this specific exception means that the page is corrupted.

    For anyone curious to look deeper into this, the specific pdf I was testing with can be found at https://www.lavarosso.com/Musica/tromba/Arban.pdf, and it's starting at page 312 where certain ones are corrupted; should be obvious exactly which ones are messed up.

    Edit: I created an issue to have the documentation updated to indicate this as a possibility: https://issuetracker.google.com/issues/386853931