Исключение из плохого токена - Невозможно добавить окно (Marshmallow - Плавающая панель инструментов)

Я изо всех сил пытался выяснить причину этого исключения, я никогда не мог воспроизвести себя, но некоторые из моих клиентов испытывают это. Это происходит только на Android 6.0.1, и поскольку авария происходит внутри самого SDK, довольно сложно понять, как это происходит.

Другие решения, касающиеся этих проблем, не помогли, например:

исключение слабого токена менеджера окон

"android.view.WindowManager $BadTokenException: невозможно добавить окно" на buider.show()

Я использую DialogFragments и AlertDialogs, я думаю, это может быть основной проблемой, но она просто не складывается, поскольку она влияет только на пользователей Marshmallow.

Fatal Exception: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
       at android.view.ViewRootImpl.setView(ViewRootImpl.java:849)
       at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:337)
       at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
       at android.widget.PopupWindow.invokePopup(PopupWindow.java:1329)
       at android.widget.PopupWindow.showAtLocation(PopupWindow.java:1077)
       at android.widget.PopupWindow.showAtLocation(PopupWindow.java:1035)
       at com.android.internal.widget.FloatingToolbar$FloatingToolbarPopup.show(FloatingToolbar.java:561)
       at com.android.internal.widget.FloatingToolbar.show(FloatingToolbar.java:212)
       at com.android.internal.view.FloatingActionMode$FloatingToolbarVisibilityHelper.updateToolbarVisibility(FloatingActionMode.java:411)
       at com.android.internal.view.FloatingActionMode$1.run(FloatingActionMode.java:65)
       at android.os.Handler.handleCallback(Handler.java:739)
       at android.os.Handler.dispatchMessage(Handler.java:95)
       at android.os.Looper.loop(Looper.java:158)
       at android.app.ActivityThread.main(ActivityThread.java:7224)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

Ответы

Ответ 1

Итак, после приобретения устройства Samsung (S6) и возможности воспроизвести эту проблему, я придумал решение, описанное ниже.

Сама ошибка возникает из плавающей панели инструментов и режимов действий, которые были добавлены в Marshmallow. При выборе текста на устройстве зефира появляется нечто вроде этого:

плавающая панель инструментов

Если пользователь затем переходит к другому действию (например, назад), не закрывая плавающую панель инструментов, приложение выходит из строя на устройствах Samsung. Это в основном пытается добавить плавучую панель инструментов к предстоящей активности.

Подробнее о плавающей панели: https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-text-selection

Таким образом, решение состоит в том, чтобы управлять жизненным циклом режима действия и убедиться, что он заканчивается до начала любой новой активности. У меня есть базовый класс активности, который наследует все мои действия, поэтому имеет смысл добавить туда решение.

public class BaseActivity extends AppCompatActivity {

    private ActionMode mActionMode;

    /**
     * Store action mode if it is started
     * @param mode
     */
    @Override
    public void onActionModeStarted(ActionMode mode) {
        super.onActionModeStarted(mode);
        mActionMode = mode;
    }

    /**
     * When activity is paused, make sure action mode is ended properly.
     * This check would feel better to have in onDestroy(), but that seems to be
     * too late down the life cycle and the crash keeps on occurring. The drawback 
     * of this solution is that the action mode is finished when app is minimized etc.
     */
    @Override
    protected void onPause() {
        super.onPause();
        endActionMode();
    }

    /**
     * Makes sure action mode is ended
     */
    private void endActionMode() {
        if (mActionMode != null) {
            mActionMode.finish(); /** immediately calls {@link #onActionModeFinished(ActionMode)} */
        }
    }

    /**
     * Clear action mode every time it finishes.
     * @param mode
     */
    @Override
    public void onActionModeFinished(ActionMode mode) {
        super.onActionModeFinished(mode);
        mActionMode = null;
    }
}