fix(android): two lifecycle crashes when the app is backgrounded or rotated with the preview running - #423
Open
abumalick wants to merge 1 commit into
Conversation
Two independent crashes, both reached when the app is backgrounded (or rotated) while the preview is up. 1. IllegalStateException: Can not perform this action after onSaveInstanceState stop() posts its whole body to the UI thread, so the fragment transaction can commit after the activity has saved its state. Because it throws on the plugin's own UI-thread runnable, no try/catch around the JS call can catch it — the app dies. The transaction only removes the preview fragment, so there is nothing worth preserving across a process death; commitAllowingStateLoss() is the correct commit here. 2. RuntimeException: Camera is being used after Camera.release() CameraActivity.onPause() releases the camera and calls mPreview.setCamera(null, -1) to announce it, but setCamera() is wrapped in `if (camera != null)`, so the null is ignored and Preview.mCamera keeps pointing at the released Camera. A later configuration change reaches setCameraDisplayOrientation() and calls setDisplayOrientation() on it. Clearing mCamera is necessary but not sufficient: setCameraDisplayOrientation() dereferences mCamera unconditionally and is reachable from CameraActivity.onConfigurationChanged() while the activity is paused, so clearing the field alone turns the RuntimeException into a NullPointerException on the same line. Both the assignment and the guard are needed.
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.
Two independent Android crashes that both fire when the app is backgrounded (or rotated) while the preview is running. Both are reproduced on a device below, with the fix verified against the same steps.
Found while chasing the top crash in a Capacitor app that keeps a
CameraPreviewon a photo-capture screen: 211 crashes across 34 users on the first one, on every release from 1.0.0 onward.1.
IllegalStateException: Can not perform this action after onSaveInstanceStatestop()posts its entire body to the UI thread, so the fragment transaction can commit after the activity has saved its state:Because it is thrown on the plugin's own posted runnable, no
try/catcharound the JSCameraPreview.stop()call can catch it — the app dies.The trap this creates is worth spelling out, because it is the natural thing to try: the obvious app-side workaround is to listen for
appStateChangeand callstop()when the app is backgrounded, so the preview never survives into the background. That makes this crash more likely, not less — it movesstop()to exactly the moment the activity is saving its state. Measured on the device below, a build doing that crashed here on the first attempt (2/2), while the same build without the listener did not reproduce it at all in 4 attempts.The transaction only removes the preview fragment, so there is no state worth preserving across a process death.
commitAllowingStateLoss()is the appropriate commit.2.
RuntimeException: Camera is being used after Camera.release()CameraActivity.onPause()releases the camera and callsmPreview.setCamera(null, -1)to announce it — butsetCamera()is wrapped inif (camera != null), so the null is ignored andPreview.mCamerakeeps pointing at the releasedCamera. A later configuration change reaches it:Note an
mCamera == nullguard at the call site would not have helped: the reference is non-null and dangling.This overlaps #315, which diagnosed the same cause — credit there, and I'm happy to close this half in its favour. But #315 has been open since 2023, and on its own it is not sufficient:
setCameraDisplayOrientation()dereferencesmCameraunconditionally and is reachable fromonConfigurationChanged()while the activity is paused, so clearing the field just changes the exception type. Built with only #315 applied, the identical steps still crash on the same line:So both the assignment and the guard are needed. This PR keeps the existing
if (camera != null)shape and adds anelsebranch rather than hoisting the assignment, but #315's form works equally well — happy to rebase onto it if you would rather merge that first.Reproduction
Device: Redmi Note 8, Android 13. Plugin 8.0.1,
start({ toBack: true, position: 'rear' }), preview mounted on a full-screen capture view.CameraPreview.stop())Camera is being used after Camera.release()(§2)appStateChangestopCan not perform this action after onSaveInstanceState(§1), 2/2NullPointerExceptionon the same line as §2start→stop→start→stoplifecycleNotes