I'm using onDestroy inside a fragment to make sure a Handler (used to animate a button) is stopped when exiting the app, as the code below shows:
@Override
public void onDestroy () {
if (anim1 !=null && run1 !=null) {
anim1.removeCallbacks(run1);
}
super.onDestroy ();
}
anim1 is the Handler; run1 is the Runnable.
The code works as intended in my test devices (running various Android versions, but not 6.0 or 8.1 - this will be an issue, as you'll see). It also works for the vast majority of users, judging by Google Console reports and general feedback.
In other words, if the user attempts to leave the app (or move to another fragment) while the Handler/Runnable are still there, there is no exception thrown and the anim1.removeCallbacks(run1);
is triggered as expected with no errors.
Looking at the reports from Google Console, however, a few Android 6.0 and 8.1 users have had a SuperNotCalledException (log provided below). Numerically, 8.1 users seem to be more affected compared to 6.0
I know that the problem is in my implementation of onDestroy because this error (SuperNotCalledException) began showing up on Google Console only after I implemented the code above. Before I was getting a NPE because of the Handler still running while the app exited (this error no longer exists).
I'm only getting a few error reports for this, so apparently it only affects a few users. But I wonder what I'm doing wrong and how to fix it.
I've already looked at other similar questions, such as these:
Fatal error: supernotcalledexception
but they don't seem to be related to my issue.
super.onDestroy
is in my code, and it comes at the end. The logcat isn't too revealing (to me at least) either:
Huawei Honor 7A (HWDUA-M), Android 8.1
Report 1 of 1
java.lang.RuntimeException:
at android.app.ActivityThread.performDestroyActivity (ActivityThread.java:4679)
at android.app.ActivityThread.handleDestroyActivity (ActivityThread.java:4697)
at android.app.ActivityThread.-wrap5 (Unknown Source)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1837)
at android.os.Handler.dispatchMessage (Handler.java:106)
at android.os.Looper.loop (Looper.java:166)
at android.app.ActivityThread.main (ActivityThread.java:6861)
at java.lang.reflect.Method.invoke (Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:450)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:936)
Caused by: android.support.v4.app.SuperNotCalledException:
at android.support.v4.app.Fragment.performDestroy (Fragment.java:2590)
at android.support.v4.app.FragmentManagerImpl.moveToState (FragmentManager.java:1566)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState (FragmentManager.java:1759)
at android.support.v4.app.FragmentManagerImpl.moveToState (FragmentManager.java:1836)
at android.support.v4.app.FragmentManagerImpl.dispatchStateChange (FragmentManager.java:3244)
at android.support.v4.app.FragmentManagerImpl.dispatchDestroy (FragmentManager.java:3235)
at android.support.v4.app.FragmentController.dispatchDestroy (FragmentController.java:265)
at android.support.v4.app.FragmentActivity.onDestroy (FragmentActivity.java:390)
at android.support.v7.app.AppCompatActivity.onDestroy (AppCompatActivity.java:209)
at android.app.Activity.performDestroy (Activity.java:7335)
at android.app.Instrumentation.callActivityOnDestroy (Instrumentation.java:1249)
at android.app.ActivityThread.performDestroyActivity (ActivityThread.java:4666)
Any idea what I could try to troubleshoot the issue further? I feel a bit baffled, because it's literally 3 lines of code causing the problem, and I can't figure out how else to put them together.
If you need any further details or code, let me know and I'd be happy to provide them.
I'm adding my solution as an answer in case someone else finds it useful. Turns out, the problem wasn't in this particular fragment and code, but in another similar fragment.
I had copied/pasted the code in all of them, but at some point, somewhere, the code I discovered searching was like this:
@Override
public void onDestroy () {
if (anim1 !=null && run1 !=null) {
anim1.removeCallbacks(run1);
super.onDestroy ();
}
}
Since super.onDestroy ()
accidentally ended up inside the if-statement, it was unreachable whenever the activity was destroyed while anim1
and/or run1
were null. Detecting it wasn't made easier by the fact that the logcat doesn't report on which fragment the exception occurred.
The morality of the tale is, always check all instances of your code, even if you have copied/pasted it.