DialogFrag # show() из фрагментации "IllegalStateException: не удается выполнить это действие после onSaveInstanceState"

Просто, чтобы быть ясным, я прочитал десятки лучших SO-вопросов на тему "IllegalStateException: не удалось выполнить это действие после onSaveInstanceState", и я прочитал сообщение блога Алекс Локвуда по проблеме http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html

Поэтому я не спрашиваю это вслепую.

У меня очень простой случай использования, когда не включает AsyncTask или любую фоновую обработку.

У меня есть фрагмент, содержащий кнопку. На кнопке onClickListener для кнопки я создаю диалоговое окно DialogFragment и покажу его.

public final class OverviewFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View view = inflater.inflate(R.layout.overview_fragment, container, false);

        startNewGameButton = (Button) view.findViewById(R.id.buttonNewGame);
        startNewGameButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                final NewGameFragment dialogFrag = NewGameFragment.create(getApplication());
                dialogFrag.show(getFragmentManager(), NewGameFragment.FRAGMENT_TAG);
            }
        });
}

[NewGameFragment]

public final class NewGameFragment extends DialogFragment {

    public static final String FRAGMENT_TAG = "NewGameFragment";

    private static final String MESSAGE = "message";

    public static NewGameFragment create(Context context) {
        final AppsPreferences prefs = new AppPreferences(context);
        final int startOption = prefs.getGameStartOption();

        final Bundle bundle = new Bundle();
        bundle.putString(MESSAGE, getMessage(context, startOption));

        final NewGameFragment fragment = new NewGameFragment();
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public final Dialog onCreateDialog(Bundle savedInstanceState) {
        final String message = getArguments().getString(MESSAGE);

        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
            .setTitle(R.string.progress_startGame_title)
            .setMessage(message);

        builder.setPositiveButton(R.string.progress_startGame_raceButton, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                new RaceAction().execute();
            }
        });
        builder.setNegativeButton(R.string.progress_startGame_eventButton, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                new EventAction().execute();
            }
        });

        final Dialog dialog = builder.create();
        dialog.setCanceledOnTouchOutside(false); // Whether clicking outside the dialog closes the dialog.
        return dialog;
    }
  }

[StackTrace]

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.void checkStateLoss()(SourceFile:1365)
at android.support.v4.app.FragmentManagerImpl.void enqueueAction(java.lang.Runnable,boolean)(SourceFile:1383)
at android.support.v4.app.BackStackRecord.int commitInternal(boolean)(SourceFile:636)
at android.support.v4.app.BackStackRecord.int commit()(SourceFile:615)
at android.support.v4.app.DialogFragment.void show(android.support.v4.app.FragmentManager,java.lang.String)(SourceFile:138)
at au.com.xandar.thegame.overview.OverviewFragment$1.void onClick(android.view.View)(SourceFile:160)
at android.view.View.performClick(View.java:4162)
at android.view.View$PerformClick.run(View.java:17082)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4867)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1007)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:774)
at dalvik.system.NativeStart.main(Native Method)

NB: Фрагмент и Диалоговый Фрагмент исходят из поддержки-v4: 21.0.0

Я вижу это на ряде устройств под управлением 4.4. Но по крайней мере один экземпляр произошел на Nexus 7 с запуском 5.0.

Я не смог воспроизвести это сам. Даже не введя искусственную задержку в onClick и пытающуюся повернуть, вернуться, домой приложение.

Итак, поскольку FragmentTransaction (для DialogFrag#show()) создается и фиксируется в потоке пользовательского интерфейса непосредственно из onClick(), как Fragment уже прошел мимо onSaveInstanceState()?

Означает ли это, что мне нужно проверить состояние жизненного цикла активности в начале каждого ввода пользователя? - очень плохо (Lifecycle предназначен для обработки этого для меня. Я не должен получать пользовательский ввод, если Activity уже прошел onPause())

Означает ли это, что мне нужно проверить состояние жизненного цикла активности до каждого оператора во время выполнения ввода пользователем? - сломанный плохой!

Что я могу сделать, чтобы остановить это?

Дополнительная информация:

После запуска в дикой природе в течение нескольких дней я могу категорически сказать, что getChildFragmentManager() не является решением.

Отказ происходит для следующих версий Android:

  • 4.4.2 90%
  • 4.4.4 5%
  • 5,0 5%

Ответы

Ответ 1

Хорошо, насколько я могу сказать, это ошибка в AOSP, поскольку я также видел экземпляр этого из чистого Android stack (т.е. ничего из моего кода вообще).

Итак, похоже, что в жизненном цикле Activity/Fragment существует проблема с потоком, в которой поток пользовательского интерфейса может получить приоритет для ответа на кнопку " ПОСЛЕ), действие/фрагмент уже сохранил ее.

Моя работа, которая была на 100% успешной, заключается в том, чтобы поймать IllegalStateException и запланировать показ диалога в следующий раз, когда Activity/Fragment станет активным с помощью PauseHandler fooobar.com/info/45707/...