Search code examples
androidandroid-activitycrashandroid-lifecycleandroid-windowmanager

IndexOutOfBoundsException inside setStoppedState of WindowManagerGlobal


The top crash in our app only happens for Android 9, and only for a handful of devices, like ZTE, TCL, Hisense, BLU, HYUNDAI.

As we don't have access to any of these devices, we were not able to reproduce this, and as it occurs inside the Android framework, we are not sure it is an error in our code (though it must be, as we could not find any similar crashes in Stackoverflow).

Fatal Exception: java.lang.RuntimeException: Unable to stop activity {MyActivity}: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
       at android.app.ActivityThread.callActivityOnStop(ActivityThread.java:4213)
       at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:4183)
       at android.app.ActivityThread.handleStopActivity(ActivityThread.java:4263)
       at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:192)
       at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
       at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
       at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loop(Looper.java:193)
       at android.app.ActivityThread.main(ActivityThread.java:6852)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:504)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
       at java.util.ArrayList.get(ArrayList.java:437)
       at android.view.WindowManagerGlobal.setStoppedState(WindowManagerGlobal.java:604)
       at android.app.Activity.performStop(Activity.java:8831)
       at android.app.ActivityThread.callActivityOnStop(ActivityThread.java:4205)
       at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:4183)
       at android.app.ActivityThread.handleStopActivity(ActivityThread.java:4263)
       at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:192)
       at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
       at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
       at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loop(Looper.java:193)
       at android.app.ActivityThread.main(ActivityThread.java:6852)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:504)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Anyone has experienced some similar crash, or has any idea of what the root cause may be?


Solution

  • Here's the code where the crash happens:

    public void setStoppedState(IBinder token, boolean stopped) {
        synchronized (mLock) {
            int count = mViews.size();
            for (int i = 0; i < count; i++) {
                if (token == null || mParams.get(i).token == token) {
                    ViewRootImpl root = mRoots.get(i);
                    root.setWindowStopped(stopped);
                }
            }
        }
    }
    

    (the line numbers don't match your exception info exactly, because I didn't know the exact version of the code that your device uses.)

    Assuming that we can expect mViews, mParams, and mRoots to be of the same length, this code looks correct.

    However, if we move forward a bit in the git history, we find commit 978e7f87:

    From: [email protected]
    Subject: Fix: WindowManagerGlobal#setStoppedState failed by IOOBE
    
    Symptom:
    An application crashed due to IndexOutOfBoundsException.
    The exception was thrown at WindowManagerGlobal#setStoppedState.
    
    Root cause:
    setStoppedState invokes setWindowStopped for each ViewRoot by
    ascending order. If an application removes its view within the
    loop, loop index exceeds the number of items.
    
    Solution:
    Loop in descending order.
    

    I would assume that the devices where you're seeing the crash do not include this commit, while non-problematic devices do.

    The new code looks like this:

    public void setStoppedState(IBinder token, boolean stopped) {
        synchronized (mLock) {
            int count = mViews.size();
            for (int i = count - 1; i >= 0; i--) {
                if (token == null || mParams.get(i).token == token) {
                    ViewRootImpl root = mRoots.get(i);
                    // Client might remove the view by "stopped" event.
                    root.setWindowStopped(stopped);
                }
            }
        }
    }
    

    Since you can't affect the devices in question, you'll need to work around this problem. The commit message points us in a clear direction: make sure that you aren't removing views in the callbacks that are happening here.