При отображении диалогового окна появляется сообщение «Невозможно выполнить это действие после onSaveInstanceState»

121

Некоторые пользователи сообщают, что если они воспользуются быстрым действием на панели уведомлений, они приближаются к силе.

Я показываю быстрое действие в уведомлении, которое вызывает класс TestDialog . В классе TestDialog после нажатия кнопки «отложить» я покажу SnoozeDialog.

private View.OnClickListener btnSnoozeOnClick() {
    return new View.OnClickListener() {

        public void onClick(View v) {
            showSnoozeDialog();
        }
    };
}

private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    snoozeDialog.show(fm, "snooze_dialog");
}

Ошибка *IllegalStateException: Can not perform this action after onSaveInstanceState*.

Строка кода, в которой запускается исключение IllegarStateException:

snoozeDialog.show(fm, "snooze_dialog");

Класс расширяет «FragmentActivity», а класс «SnoozeDialog» расширяет «DialogFragment».

Вот полная трассировка стека ошибки:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:127)
at com.test.testing.TestDialog.f(TestDialog.java:538)
at com.test.testing.TestDialog.e(TestDialog.java:524)
at com.test.testing.TestDialog.d(TestDialog.java:519)
at com.test.testing.g.onClick(TestDialog.java:648)
at android.view.View.performClick(View.java:3620)
at android.view.View$PerformClick.run(View.java:14292)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4507)
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:790)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
at dalvik.system.NativeStart.main(Native Method)

Я не могу воспроизвести эту ошибку, но получаю много сообщений об ошибках.

Кто-нибудь может помочь, как я могу исправить эту ошибку?

chrisonline
источник
2
Вы нашли решение? У меня такая же проблема, как и у вас. Я спросил здесь: stackoverflow.com/questions/15730878/… Пожалуйста, проверьте мой вопрос и посмотрите возможное решение, которое не работает в моем случае. Может быть, это сработает для вас.
rootpanthera 01
Пока нет решения :-( И ваше предложение уже добавлено в мой класс.
chrisonline
Проверьте принятый ответ отсюда. Это решило мою проблему: stackoverflow.com/questions/14177781/…
bogdan
4
Видна ли ваша активность при открытии этого диалога? Похоже, это может быть вызвано тем, что ваше приложение пытается отобразить диалоговое окно, прикрепленное к операции, которая была приостановлена ​​/ остановлена.
Кай
stackoverflow.com/questions/7575921/… я уверен, что вы пробовали это.
Орион

Ответы:

66

Это обычная проблема . Мы решили эту проблему, переопределив show () и обработав исключение в расширенном классе DialogFragment.

public class CustomDialogFragment extends DialogFragment {

    @Override
    public void show(FragmentManager manager, String tag) {
        try {
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        } catch (IllegalStateException e) {
            Log.d("ABSDIALOGFRAG", "Exception", e);
        }
    }
}

Обратите внимание, что применение этого метода не изменит внутренние поля DialogFragment.class:

boolean mDismissed;
boolean mShownByMe;

В некоторых случаях это может привести к неожиданным результатам. Лучше использовать commitAllowingStateLoss () вместо commit ()

Рафаэль
источник
3
Но почему возникает эта проблема? Можно ли игнорировать ошибку? Что происходит, когда вы это делаете? В конце концов, при нажатии это означает, что активность активна
разработчик Android
1
Не могли бы вы тогда пометить и / или прокомментировать?
разработчик Android
2
лучше вызвать super.show (manager, tag) внутри предложения try-catch. Таким образом, флаги, принадлежащие DialogFragment, могут оставаться в безопасности
Shayan_Aryan
20
На этом этапе вы можете вызвать commitAllowingStateLoss () вместо commit (). Исключение не возникает.
ARLabs
1
@ARLabs Я полагаю, что в этом случае это было бы лучше и для ответчика, поскольку, если вы просто поймаете исключение, как показано здесь, диалоговое окно определенно не будет отображаться. Лучше показать диалог, если можно, и он может исчезнуть, если состояние нужно восстановить. Также ограничьте использование памяти вашим приложением в фоновом режиме, чтобы оно не могло быть уничтожено.
androidguy 08
27

Это означает, что вы commit()( show()в случае DialogFragment) фрагмент после onSaveInstanceState().

Android сохранит состояние вашего фрагмента в onSaveInstanceState(). Итак, если вы commit()фрагментируете послеonSaveInstanceState() фрагмента, состояние будет потеряно.

В результате, если Activity будут убиты и воссозданы позже, фрагмент не будет добавлен к Activity, что плохо для пользователя. Вот почему Android не допускает потери состояния любой ценой.

Простое решение - проверить, сохранено ли уже состояние.

boolean mIsStateAlreadySaved = false;
boolean mPendingShowDialog = false;

@Override
public void onResumeFragments(){
    super.onResumeFragments();
    mIsStateAlreadySaved = false;
    if(mPendingShowDialog){
        mPendingShowDialog = false;
        showSnoozeDialog();
    }
}

@Override
public void onPause() {
    super.onPause();
    mIsStateAlreadySaved = true;
}

private void showSnoozeDialog() {
    if(mIsStateAlreadySaved){
        mPendingShowDialog = true;
    }else{
        FragmentManager fm = getSupportFragmentManager();
        SnoozeDialog snoozeDialog = new SnoozeDialog();
        snoozeDialog.show(fm, "snooze_dialog");
    }
}

Примечание: onResumeFragments () будет вызываться, когда фрагменты возобновятся.

Pongpat
источник
1
Что, если я хочу показать DialogFragment в другом фрагменте?
разработчик Android
Наше решение - создать базовый класс активности и фрагмента и делегировать onResumeFragments фрагменту (мы создаем onResumeFragments в базовом классе фрагмента). Это неприятное решение, но оно работает. Если у вас есть лучшее решение, дайте мне знать :)
Pongpat
Что ж, я подумал, что отображение диалогового окна в "onStart" должно работать нормально, так как фрагмент обязательно отображается, но я все еще вижу некоторые отчеты о сбоях по этому поводу. Мне было предложено вместо этого попробовать поместить его в onResume. По поводу альтернатив я видел это: twigstechtips.blogspot.co.il/2014/01/… , но это довольно странно.
разработчик Android
Я думаю, причина того, что twigstechtips.blogspot.co.il/2014/01/… работает, потому что он запускает новый поток и, следовательно, весь код жизненного цикла, то есть onStart, onResume и т. Д., Вызываемый до того, как код runOnUiThread когда-либо запускался. Это означает, что состояние уже восстановлено до вызова runOnUiThread.
Pongpat
2
Я использую одиночный вызов для публикации (запускается). Что касается getFragmentManager, это зависит. Если вы хотите поделиться этим диалогом с другим действием, вам следует использовать getFragmentManager, однако, если этот диалог существует только с фрагментом, getChildFragmentManager кажется лучшим выбором.
Понгпат,
16
private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    // snoozeDialog.show(fm, "snooze_dialog");
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(snoozeDialog, "snooze_dialog");
    ft.commitAllowingStateLoss();
}

ссылка: ссылка

хуу дуй
источник
11

Через несколько дней я хочу поделиться своим решением , как я установил его, чтобы показать DialogFragment Вам необходимо переопределить show()метод его и вызвать commitAllowingStateLoss()на Transactionобъект. Вот пример на Котлине:

override fun show(manager: FragmentManager?, tag: String?) {
        try {
            val ft = manager?.beginTransaction()
            ft?.add(this, tag)
            ft?.commitAllowingStateLoss()
        } catch (ignored: IllegalStateException) {

        }

    }
Деннис Зиньковски
источник
1
Так что разработчики не должны наследоваться от DialogFragmentвы могли бы изменить это функция расширения Котлин со следующей подписью: fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String). Кроме того, try-catch не требуется, поскольку вы вызываете commitAllowingStateLoss()метод, а не commit()метод.
Адиль Хусейн
10

Если диалог не очень важен (нормально не показывать его, когда приложение закрыто / больше не отображается), используйте:

boolean running = false;

@Override
public void onStart() {
    running = true;
    super.onStart();
}

@Override
public void onStop() {
    running = false;
    super.onStop();
}

И открывать свой диалог (фрагмент) только тогда, когда мы выполняем:

if (running) {
    yourDialog.show(...);
}

ИЗМЕНИТЬ, ВОЗМОЖНО ЛУЧШЕЕ РЕШЕНИЕ:

Когда onSaveInstanceState вызывается в жизненном цикле, это непредсказуемо, я думаю, что лучшим решением будет проверить isSavedInstanceStateDone () следующим образом:

/**
 * True if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
private boolean savedInstanceStateDone;

@Override
protected void onResume() {
    super.onResume();

    savedInstanceStateDone = false;
}

@Override
protected void onStart() {
    super.onStart();

    savedInstanceStateDone = false;
}

protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    savedInstanceStateDone = true;
}


/**
 * Returns true if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
public boolean isSavedInstanceStateDone() {
    return savedInstanceStateDone;
}
Фрэнк
источник
Похоже, это не работает, поскольку я получаю это исключение при вызове метода onStart (пытаюсь показать там DialogFragment).
разработчик Android
Ты спас мне день. Спасибо, Фрэнк.
Cüneyt
7

Я сталкивался с этой проблемой годами.
Интернет завален десятками (сотнями? Тысячами?) Дискуссий по этому поводу, и путаницы и дезинформации в них, кажется, предостаточно.
Чтобы усугубить ситуацию, и в духе комикса xkcd «14 стандартов» я бросаю свой ответ на ринг.
xkcd 14 стандарты

cancelPendingInputEvents(), commitAllowingStateLoss(), catch (IllegalStateException e), И другие подобные решения , все , кажется , зверские.

Надеюсь, следующее легко показывает, как воспроизвести и исправить проблему:

private static final Handler sHandler = new Handler();
private boolean mIsAfterOnSaveInstanceState = true;

@Override
protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    mIsAfterOnSaveInstanceState = true; // <- To repro, comment out this line
}

@Override
protected void onPostResume()
{
    super.onPostResume();
    mIsAfterOnSaveInstanceState = false;
}

@Override
protected void onResume()
{
    super.onResume();
    sHandler.removeCallbacks(test);
}

@Override
protected void onPause()
{
    super.onPause();
    sHandler.postDelayed(test, 5000);
}

Runnable test = new Runnable()
{
    @Override
    public void run()
    {
        if (mIsAfterOnSaveInstanceState)
        {
            // TODO: Consider saving state so that during or after onPostResume a dialog can be shown with the latest text
            return;
        }

        FragmentManager fm = getSupportFragmentManager();
        DialogFragment dialogFragment = (DialogFragment) fm.findFragmentByTag("foo");
        if (dialogFragment != null)
        {
            dialogFragment.dismiss();
        }

        dialogFragment = GenericPromptSingleButtonDialogFragment.newInstance("title", "message", "button");
        dialogFragment.show(fm, "foo");

        sHandler.postDelayed(test, 5000);
    }
};
swooby
источник
2
Я люблю людей, которые голосуют против без объяснения причин. Вместо того, чтобы просто проголосовать против, может быть, было бы лучше, если бы они объяснили, чем мое решение ошибочно? Могу ли я проголосовать против того, кто проголосовал против?
swooby
1
Да, это проблема ТАК, я каждый раз пишу эту проблему в предложениях, но они не хотят решать.
CoolMind
2
Я думаю, что отрицательные голоса могут быть результатом встроенного XKCD, ответы на самом деле не место для социальных комментариев (независимо от того, насколько они забавны и / или правдивы).
RestingRobot
6

попробуйте использовать FragmentTransaction вместо FragmentManager. Я думаю, что приведенный ниже код решит вашу проблему. Если нет, дайте мне знать.

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");

РЕДАКТИРОВАТЬ:

Фрагмент транзакции

Пожалуйста, проверьте эту ссылку. Я думаю, это решит ваши вопросы.

RIJO RV
источник
4
Было бы замечательно любое объяснение того, почему использование FragmentTransaction устраняет проблему.
Hemanshu
3
Dialog # show (FragmentManager, tag) делает то же самое. Это не выход.
Уильям
3
Этот ответ не является решением. DialogFragment # show (ft) и show (fm) делают одно и то же.
danijoo
@danijoo Вы правы, что оба они делают одну и ту же работу. Но на некоторых телефонах есть проблема, похожая на эту, если вы используете fragmentmanager вместо fragmenttransaction. Итак, в моем случае это решило мою проблему.
RIJO RV 02
6

Использование новых областей жизненного цикла Activity-KTX так же просто, как следующий пример кода:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

Этот метод может быть вызван непосредственно после onStop () и будет успешно отображать диалог после вызова onResume () после возврата.

PenguinDan
источник
3

Многие представления публикуют высокоуровневые события, такие как обработчики кликов, в очередь событий для отложенного запуска. Итак, проблема в том, что «onSaveInstanceState» уже был вызван для Activity, но очередь событий содержит отложенное «событие щелчка». Следовательно, когда это событие отправляется вашему обработчику

at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)

и ваш код вызывает showисключение IllegalStateException.

Самое простое решение - очистить очередь событий в onSaveInstanceState

protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // ..... do some work
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            findViewById(android.R.id.content).cancelPendingInputEvents();
        }
}
сим
источник
Вы действительно подтвердили, что это решает проблему?
mhsmith
Google добавил это в следующий выпуск библиотек androidx, который в настоящее время находится в стадии бета ( activityи fragment).
mhsmith
1
@mhsmith Я помню, что это решение решило проблему в моем коде с помощью IllegalStateException
sim
2

Сделайте объект фрагмента диалога глобальным и вызовите dismissAllowingStateLoss () в методе onPause ()

@Override
protected void onPause() {
    super.onPause();

    if (dialogFragment != null) {
        dialogFragment.dismissAllowingStateLoss();
    }
}

Не забудьте указать значение во фрагменте и вызвать show () при нажатии кнопки или где-либо еще.

Рохит Раджпал
источник
1

Хотя официально это нигде не упоминается, но я сталкивался с этой проблемой пару раз. По моему опыту, что-то не так в библиотеке совместимости, поддерживающей фрагменты на старых платформах, что вызывает эту проблему. Вы можете проверить это с помощью обычного API-интерфейса диспетчера фрагментов. Если ничего не работает, вы можете использовать обычный диалог вместо фрагмента диалога.

Далвиндер Сингх
источник
1
  1. Добавьте этот класс в свой проект: (должен быть в пакете android.support.v4.app )
пакет android.support.v4.app;


/ **
 * Создано Gil 16.08.2017.
 * /

открытый класс StatelessDialogFragment расширяет DialogFragment {
    / **
     * Отобразите диалоговое окно, добавив фрагмент с помощью существующей транзакции и затем зафиксировав
     * транзакция, допускающая потерю состояния.
* * Я бы рекомендовал вам использовать {@link #show (FragmentTransaction, String)} большую часть времени, но * это для диалогов, которые вас действительно не волнуют. (Отладка / отслеживание / реклама и т. Д.) * * @param транзакция * Существующая транзакция, в которую нужно добавить фрагмент. * тег @param * Тег для этого фрагмента согласно * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @return Возвращает идентификатор совершенной транзакции согласно * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @ см. StatelessDialogFragment # showAllowingStateLoss (FragmentManager, String) * / public int showAllowingStateLoss (транзакция FragmentTransaction, строковый тег) { mDismissed = false; mShownByMe = true; transaction.add (это, тег); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss (); return mBackStackId; } / ** * Отобразите диалог, добавив фрагмент в данный FragmentManager. Это удобство * для явного создания транзакции, добавления к ней фрагмента с заданным тегом, и * совершая это, не заботясь о состоянии. Это не добавляет транзакцию в * задний стек. После закрытия фрагмента будет выполнена новая транзакция для его удаления. * из деятельности.
* * Я бы рекомендовал вам использовать {@link #show (FragmentManager, String)} большую часть времени, но это * для диалогов, которые вас действительно не волнуют. (Отладка / отслеживание / реклама и т. Д.) * * * @param manager * FragmentManager, в который будет добавлен этот фрагмент. * тег @param * Тег для этого фрагмента согласно * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @ см. StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) * / public void showAllowingStateLoss (менеджер FragmentManager, строковый тег) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction (); ft.add (это, тег); ft.commitAllowingStateLoss (); } }
  1. Расширить StatelessDialogFragment вместо DialogFragment
  2. Используйте метод showAllowingStateLoss вместо show

  3. Наслаждаться ;)

Гил Ш.
источник
Для чего нужны все эти логические поля? Почему они не объявлены как члены класса?
undefined
1
Логические поля являются защищенными членами DialogFragment, их имена, очевидно, указывают на то, для чего они нужны, и нам необходимо обновить их, чтобы не мешать логике DialogFragment. Обратите внимание, что в исходном классе DialogFragment эти функции существуют, но без публичного доступа
Gil SH
Ой, эти члены не защищены, они внутренние. Я получал ошибки компиляции, когда помещал StatelessDialogFragmentвнутрь одного из своих пакетов. Спасибо, чувак. Я скоро протестирую его в продакшене.
undefined
1

используйте этот код

FragmentTransaction ft = fm.beginTransaction();
        ft.add(yourFragment, "fragment_tag");
        ft.commitAllowingStateLoss();

вместо того

yourFragment.show(fm, "fragment_tag");
Факсриддин Абдуллаев
источник
1

Я нашел элегантное решение этой проблемы с помощью отражения. Проблема всех вышеперечисленных решений в том, что поля mDismissed и mShownByMe не меняют своего состояния.

Просто переопределите метод "show" в собственном настраиваемом фрагменте диалогового окна нижнего листа, как в примере ниже (Kotlin)

override fun show(manager: FragmentManager, tag: String?) {
        val mDismissedField = DialogFragment::class.java.getDeclaredField("mDismissed")
        mDismissedField.isAccessible = true
        mDismissedField.setBoolean(this, false)

        val mShownByMeField = DialogFragment::class.java.getDeclaredField("mShownByMe")
        mShownByMeField.isAccessible = true
        mShownByMeField.setBoolean(this, true)

        manager.beginTransaction()
                .add(this, tag)
                .commitAllowingStateLoss()
    }
Рома Богдан
источник
4
«Я нашел элегантное решение этой проблемы с помощью отражения». как это элегантно?
Марк Буйкема,
элегантный, стильный, шикарный, шикарный, милый, изящный
Рома Богдан
1
это единственное решение, которое сработало для меня. Я думаю, это элегантно
MBH
0

Следующая реализация может использоваться для решения проблемы безопасного выполнения изменений состояния в течение Activityжизненного цикла, в частности для отображения диалогов: если состояние экземпляра уже было сохранено (например, из-за изменения конфигурации), оно откладывает их до тех пор, пока возобновленное состояние не изменится. было выполнено.

public abstract class XAppCompatActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    /** The retained fragment for this activity */
    private ActivityRetainFragment retainFragment;

    /** If true the instance state has been saved and we are going to die... */
    private boolean instanceStateSaved;

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // get hold of retain Fragment we'll be using
        retainFragment = ActivityRetainFragment.get(this, "Fragment-" + this.getClass().getName());
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();

        // reset instance saved state
        instanceStateSaved = false;

        // execute all the posted tasks
        for (ActivityTask task : retainFragment.tasks) task.exec(this);
        retainFragment.tasks.clear();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        instanceStateSaved = true;
    }

    /**
     * Checks if the activity state has been already saved.
     * After that event we are no longer allowed to commit fragment transactions.
     * @return true if the instance state has been saved
     */
    public boolean isInstanceStateSaved() {
        return instanceStateSaved;
    }

    /**
     * Posts a task to be executed when the activity state has not yet been saved
     * @param task The task to be executed
     * @return true if the task executed immediately, false if it has been queued
     */
    public final boolean post(ActivityTask task)
    {
        // execute it immediately if we have not been saved
        if (!isInstanceStateSaved()) {
            task.exec(this);
            return true;
        }

        // save it for better times
        retainFragment.tasks.add(task);
        return false;
    }

    /** Fragment used to retain activity data among re-instantiations */
    public static class ActivityRetainFragment extends Fragment {

        /**
         * Returns the single instance of this fragment, creating it if necessary
         * @param activity The Activity performing the request
         * @param name The name to be given to the Fragment
         * @return The Fragment
         */
        public static ActivityRetainFragment get(XAppCompatActivity activity, String name) {

            // find the retained fragment on activity restarts
            FragmentManager fm = activity.getSupportFragmentManager();
            ActivityRetainFragment fragment = (ActivityRetainFragment) fm.findFragmentByTag(name);

            // create the fragment and data the first time
            if (fragment == null) {
                // add the fragment
                fragment = new ActivityRetainFragment();
                fm.beginTransaction().add(fragment, name).commit();
            }

            return fragment;
        }

        /** The queued tasks */
        private LinkedList<ActivityTask> tasks = new LinkedList<>();

        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);

            // retain this fragment
            setRetainInstance(true);
        }

    }

    /** A task which needs to be performed by the activity when it is "fully operational" */
    public interface ActivityTask {

        /**
         * Executed this task on the specified activity
         * @param activity The activity
         */
        void exec(XAppCompatActivity activity);
    }
}

Затем используйте такой класс:

/** AppCompatDialogFragment implementing additional compatibility checks */
public abstract class XAppCompatDialogFragment extends AppCompatDialogFragment {

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag) {
        return showRequest(activity, tag, null);
    }

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @param args The dialog arguments
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag, final Bundle args)
    {
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (args!= null) setArguments(args);
                show(activity.getSupportFragmentManager(), tag);
            }
        });
    }

    /**
     * Dismiss this dialog as soon as possible
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest()
    {
        return dismissRequest(null);
    }

    /**
     * Dismiss this dialog as soon as possible
     * @param runnable Actions to be performed before dialog dismissal
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest(final Runnable runnable)
    {
        // workaround as in rare cases the activity could be null
        XAppCompatActivity activity = (XAppCompatActivity)getActivity();
        if (activity == null) return false;

        // post the dialog dismissal
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (runnable != null) runnable.run();
                dismiss();
            }
        });
    }
}

Вы можете безопасно отображать диалоги, не беспокоясь о состоянии приложения:

public class TestDialog extends XAppCompatDialogFragment {

    private final static String TEST_DIALOG = "TEST_DIALOG";

    public static void show(XAppCompatActivity activity) {
        new TestDialog().showRequest(activity, TEST_DIALOG);
    }

    public TestDialog() {}

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        return new AlertDialog.Builder(getActivity(), R.style.DialogFragmentTheme /* or null as you prefer */)
                .setTitle(R.string.title)
                // set all the other parameters you need, e.g. Message, Icon, etc.
                ).create();
    }
}

а затем позвоните TestDialog.show(this)из вашего XAppCompatActivity.

Если вы хотите создать более общий класс диалогового окна с параметрами, вы можете сохранить их в Bundleс аргументами в show()методе и извлекать их getArguments()в onCreateDialog().

Весь подход может показаться немного сложным, но как только вы создали два базовых класса для действий и диалогов, он довольно прост в использовании и отлично работает. Его можно использовать для других Fragmentопераций, на которые может повлиять та же проблема.

gicci
источник
0

Эта ошибка возникает из-за того, что события ввода (такие как нажатие клавиши или события onclick) доставляются после onSaveInstanceStateвызова.

Решение состоит в том, чтобы переопределить onSaveInstanceStateвашу Activity и отменить любые ожидающие события.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final View rootView = findViewById(android.R.id.content);
        if (rootView != null) {
            rootView.cancelPendingInputEvents();
        }
    }
}
Уильям
источник