Repository navigation
Conversation
…ative memory leak drawPdf() runs several times per mount (setPath, onAfterUpdateTransaction, onAttachedToWindow) and again on every prop transaction, and each call reloads the document. A reload cancels the in-flight DecodingAsyncTask, but AndroidPdfViewer 4.0.1's DecodingAsyncTask.onCancelled() never disposes the document it may already have opened, which leaked about 3 MB of native memory per mount. drawPdf() now skips the reload when the view is not recycled and the load configuration is unchanged. page is left out of that configuration since it tracks the displayed page and setPage() already applies it to the live view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1046
Summary
On Android, every mount of
<Pdf>leaked about 3 MB of native memory. That was one pdfium document opened by a cancelled load and never closed. This PR stopsdrawPdf()from reloading the document when nothing changed, which removes those cancelled loads.Root cause
PdfView.drawPdf()runs three times per mount (posted fromPdfManager.setPath(), posted fromPdfManager.onAfterUpdateTransaction(), and called fromonAttachedToWindow()), and again on every prop transaction. Each call builds a newConfiguratorand reloads the document, even if no prop that affects loading changed.PDFView.recycle(), which cancels the previousDecodingAsyncTask. Those tasks run onAsyncTask.THREAD_POOL_EXECUTOR, in parallel, so a cancelled task has usually already opened its document natively.DecodingAsyncTask.onCancelled()only sets a flag. ThePdfFileit opened is never disposed, so its native document leaks.Details and logs are in #1046.
Fix
drawPdf()now builds a string from every value it passes to theConfigurator(path, password, spacing, fit policy, paging flags, swipe/double-tap/annotation/RTL flags, min/max scale). It skips the reload when that string matches the last load and the view is not recycled.loadError()recycles the view) goes through as before.pageis not part of the key. It tracks the displayed page (onPageChanged), andsetPage()already applies a newpageprop to the live view. Including it would trigger a reload after every scroll.memoworkaround in Crash on Android When Closing PDF Page After Scrolling || java.lang.IllegalStateException: Already closed #976 was compensating for.Testing
Physical device: Samsung Galaxy Tab Active5 (SM-X306B), Android 16 / API 36, arm64, debug build of
FabricExample(RN 0.81.1, New Architecture), localfile://PDF (1.3 MB, 21 pages).onLoadComplete+ 1.5 s → unmount, repeatedsLibraryReferenceCountafter each unmount (open native documents)No crash in any of these runs.
Remaining limitation
Unmounting the viewer while the document is still decoding still cancels a
DecodingAsyncTask. In the stress test above, that leaves roughly one orphaned document per 7 early unmounts. That cancellation is legitimate, so the proper fix belongs in AndroidPdfViewer:DecodingAsyncTask.onCancelled()should dispose thePdfFileit opened. Working around it from here would mean reflection on a private field of that class, so I left it out of this PR.Related
Already closedrace inrecycle()) is independent of this PR. The two touch different parts ofPdfView.java, merge without conflict, and were also tested together on the same device: 1,500 stress iterations (unmount and source-switch modes) with no crash.loadError()→recycle()after a successful load).