I've encountered a rather strange issue with Activity
lifecycle.
Short prequel:
The first symptom that I've found is that it crashed with IllegalArgumentException
when I was trying to unregister receiver in onStop
after registering it in onStart
.
After dumping the full list of active receivers (some reflection magic), I've found that my receiver is not the list. It was either being removed somewhere else by mistake, or it was removed during onDestroy
call of Activity
where Context
is being cleaned (during onDestroy
ActivityThread
calls ContextImpl#performFinalCleanup
, which then calls LoadedApk#removeContextRegistrations
).
What is happening?
After adding some more analytics info to the crash, I found out that while the crash is happening in onStop
, the Activity
is in a pretty weird state - it's isDestroyed()
call returns true
, it's isFinishing()
returns false
, and it's getLifecycle().getCurrentState()
returns DESTROYED
...
Checking normal onStop()
(without crash) call shows that the Activity
is in this state:
isDestroyed()
is false
, and getLifecycle().getCurrentState()
is CREATED
in onStop
.
So I drew the conclusion that onStop
is being called after onDestroy
, which I thought is impossible, but it seems to happen.
And onStop
is definitely not being called manually by something else in the app, since this is the stack trace of where the onStop
is called from.
com.myapp.TheActivity.onStop (TheActivity.java:217)
android.app.Instrumentation.callActivityOnStop (Instrumentation.java:1474)
android.app.Activity.performStop (Activity.java:8189)
android.app.ActivityThread.callActivityOnStop (ActivityThread.java:4994)
android.app.ActivityThread.performStopActivityInner (ActivityThread.java:4967)
android.app.ActivityThread.handleStopActivity (ActivityThread.java:5047)
android.app.servertransaction.TransactionExecutor.performLifecycleSequence (TransactionExecutor.java:233)
android.app.servertransaction.TransactionExecutor.cycleToPath (TransactionExecutor.java:201)
android.app.servertransaction.TransactionExecutor.executeLifecycleState (TransactionExecutor.java:173)
android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:97)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2220)
android.os.Handler.dispatchMessage (Handler.java:107)
android.os.Looper.loop (Looper.java:237)
android.app.ActivityThread.main (ActivityThread.java:8016)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1076)
The reason is unclear, but, it is getting more and more attention recently. And it happens in both onResume/onPause and onStart/onStop pairs to call register/unregister. About those lifeCycle checks, make sure you have on Instance of the activity with hashCode() or something. Anyway, to fix the issue the best practice is to wrap register/unregister calls in try/catch block:
private void registerBroadcastReceiver() {
try {
appUpdateReceiver = new AppUpdateReceiver();
registerReceiver(appUpdateReceiver, appUpdateIntentFilter);
} catch (IllegalArgumentException e) {
// already registered
}
}
private void unregisterBroadcastReceiver() {
try {
unregisterReceiver(appUpdateReceiver);
} catch (IllegalArgumentException e) {
// already unregistered
}
}
I am not entirely sure why that happens in the first place, but I've encountered something similar on one of the Xiaomi devices that did not happen on any of the emulators (or my device), but happened on somebody else's phone. onStop()
was called 5 seconds after the app was moved to background, but onPause()
was called immediately. More weird issue, if in between these 5 seconds, I open the app from task manager, onStart()
was called. (Notice the 2 onStart()
calls but not a singular onStop()
call.) I think it would have been possible if the activity was destroyed, onDestroy()
would be called before onStop()
. As an alternative, you might try to move the receiver registration calls to onResume()
and onPause()
instead of onStart()
and onStop()
.
Second: I don't exactly use lifecycle of the activity when it comes to checking resumed or paused state, but I'd suggest overriding all of those methods (onPause, onResume etc...), storing the state on boolean variables and logging the state while checking the actual calls. Maybe that will result in a different state since this is a pretty unusual check.
If it does not happen on other activities, in that activity, maybe something is posting so many callbacks to the main message looper that prevents onStop()
from getting called in the first place because of the overflow, causing this exception to happen. These are only assumptions, of course, but I'd be grateful if they helped in a way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With