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

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

Я показываю быстрое действие в уведомлении, которое вызывает класс TestDialog. В классе TestDialog после нажатия кнопки «snooze», я покажу 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: Не удается выполнить это действие после 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) 

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

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

Solutions Collecting From Web of "При показе диалога я получаю «Не могу выполнить это действие после onSaveInstanceState»"

Это обычная проблема . Мы решили эту проблему, переопределив 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); } } 

}

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

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

В результате, если активность будет убита и воссоздана позже, фрагмент не добавит активности, которая является плохим пользовательским интерфейсом. Вот почему 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 () вызывается, когда фрагменты возобновляются.

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

 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; } 

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

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

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

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

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

 private void showSnoozeDialog() { FragmentManager fm = getSupportFragmentManager(); SnoozeDialog snoozeDialog = new SnoozeDialog(); // snoozeDialog.show(fm, "snooze_dialog"); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.add(dialog, null); ft.commitAllowingStateLoss(); } 

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

Через несколько дней я хочу поделиться своим решением, как я его исправил, чтобы показать 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) { } } 

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

Следующая реализация может быть использована для решения проблемы выполнения безопасных изменений состояния во время жизненного цикла 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, eg Message, Icon, etc. ).create(); } } 

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

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

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

  1. Добавьте этот класс в свой проект:
 Пакет android.support.v4.app;


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

 Public class StatelessDialogFragment расширяет DialogFragment {
     / **
      * Отобразите диалог, добавив фрагмент, используя существующую транзакцию, а затем выполните
      * Транзакция, разрешая потерю государства. 
* * Я бы рекомендовал использовать {@link #show (FragmentTransaction, String)} большую часть времени, но * Это для диалоговых окон, которые вам по-прежнему не волнует. (Отладка / отслеживание / реклама и т. Д.) * * @param транзакция * Существующая транзакция для добавления фрагмента. * @param tag * Тег для этого фрагмента, согласно * {@link FragmentTransaction # add (Фрагмент, Строка) FragmentTransaction.add}. * @return Возвращает идентификатор совершенной транзакции в соответствии с * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentManager, String) * / Public int showAllowingStateLoss (транзакция FragmentTransaction, тег String) { MDismissed = false; MShownByMe = true; Transaction.add (это, тег); MViewDestroyed = false; MBackStackId = transaction.commitAllowingStateLoss (); Return mBackStackId; } / ** * Отобразите диалог, добавив фрагмент к данному FragmentManager. Это удобство * Для явного создания транзакции, добавления фрагмента к нему с заданным тегом и * Совершая его, не заботясь о состоянии. Это не добавляет транзакции к * Задний стек. Когда фрагмент будет уволен, для его удаления будет выполнена новая транзакция * От деятельности.
* * Я бы рекомендовал использовать {@link #show (FragmentManager, String)} большую часть времени, но это * Для диалогов, которые вам по-прежнему не волнует. (Отладка / отслеживание / реклама и т. Д.) * * * @param manager * Этот фрагмент будет добавлен в FragmentManager. * @param tag * Тег для этого фрагмента, согласно * {@link FragmentTransaction # add (Фрагмент, Строка) FragmentTransaction.add}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) * / Public void showAllowingStateLoss (менеджер FragmentManager, тег String) { MDismissed = false; MShownByMe = true; FragmentTransaction ft = manager.beginTransaction (); Ft.add (это, тег); ft.commitAllowingStateLoss (); } }
  1. Расширить StatelessDialogFragment вместо DialogFragment
  2. Используйте метод showAllowingStateLoss вместо показа

  3. Наслаждаться 😉