I'm facing an issue when running my app (min SDK 21) on Android 10 (SDK 29).
The app's initial activity is coming up and showing as expected. Now, when pressing a button to bring up a second activity on top, I see this crash log:
java.lang.IllegalStateException: Activity top position already set to onTop=false
at android.app.ActivityThread.handleTopResumedActivityChanged(ActivityThread.java:4370)
at android.app.servertransaction.TopResumedActivityChangeItem.execute(TopResumedActivityChangeItem.java:39)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
The second activity is starting up fine for all Android versions below SDK 29. The code is as simple as
this.startActivity (new Intent (MyFirstActivity.this, MySecondActivity.class));
Any hint on what might trigger this issue and what the exception ("Activity top position already set to onTop=false") is actually telling me?
This is the ActivityThread.java code for your convenience:
@Override
public void handleTopResumedActivityChanged(IBinder token, boolean onTop, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (r == null || r.activity == null) {
Slog.w(TAG, "Not found target activity to report position change for token: " + token);
return;
}
if (DEBUG_ORDER) {
Slog.d(TAG, "Received position change to top: " + onTop + " for activity: " + r);
}
if (r.isTopResumedActivity == onTop) {
throw new IllegalStateException("Activity top position already set to onTop=" + onTop);
}
r.isTopResumedActivity = onTop;
if (r.getLifecycleState() == ON_RESUME) {
reportTopResumedActivityChanged(r, onTop, "topStateChangedWhenResumed");
} else {
if (DEBUG_ORDER) {
Slog.d(TAG, "Won't deliver top position change in state=" + r.getLifecycleState());
}
}
}
Here is an excerpt from AndroidManifest:
<application
...
<activity
android:name="com.xxx.MyFirstActivity"
android:label="@string/${applicationLabel}"
android:launchMode="singleTask"
android:configChanges="orientation|keyboardHidden|screenSize" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
...
<activity
android:name="com.xxx.MySecondActivity"
android:label="@string/ls_Quick_Reference"
android:icon="@drawable/action_help" >
</activity>
...
</application>
Why are apps on my Android crashing? Apps on Android can crash because of low storage space, too many apps running simultaneously, a weak internet connection, or not having the proper app updates installed.
This usually occurs when your Wi-Fi or cellular data is slow or unstable, causing apps to malfunction. Another reason for Android apps crashing can be a lack of storage space in your device. This can occur when you overload your device's internal memory with heavy apps.
I answer my own question here. I had filed a bug on this in Google's / Android's tracking tool and contacted the author in parallel.
https://issuetracker.google.com/issues/140571893
To summarize, part of my code screwed up Android's activity stack and the crash has been a deferred result from this.
In detail: when starting my app, it is sometimes necessary to run some migration or repair actions from former runs. As these actions need to be done on the main thread, and I wanted to inform the user he/she needs to wait a bit, I raised a view showing a message. As my app is busy on the main thread, the display has not been refreshed and the view didn't become visible. To work around this, I ran an "inner loop" to allow display messages to be dispatched:
try
{
// At this point all draw events should be in the event queue,
// now add another event to the end of queue to finish the loop once everything is processed.
Handler handler = new Handler (Looper.myLooper());
handler.postDelayed (new Runnable() {
@Override
public void run() {
throw new FinishLoopRunTimeException();
}
}, 50);
Looper.loop();
}
catch (FinishLoopRunTimeException e) {}
FinishLoopRunTimeException is a simple specialization of RuntimeException.
While this is a valid approach to flush display messages, Looper.loop () must not be ran while activity status change in Android 10. For my app, this has been the case because the main activity has been in the midst up startup when this code ran. In case you do this, the activity thread state will be screwed up and a later start of another activity is crashing.
So the solution is to make sure activities are in a stable state while an inner loop is executed for Android 10. Older Android versions did not show this problem.
While this can be considered an Android 10 bug (no mention Activity states must not be altered in Looper documentation) and should be fixed to be more robust in future versions, this problem can be worked around by resorting the Looper code.
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