IllegalStateException: невозможно выполнить это действие после onSaveInstanceState с ViewPager

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

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyUp(Activity.java:2044)
at android.view.KeyEvent.dispatch(KeyEvent.java:2529)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.widget.TabHost.dispatchKeyEvent(TabHost.java:297)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2880)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2853)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2028)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4028)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
at dalvik.system.NativeStart.main(Native Method)

По-видимому, это как-то связано с FragmentManager, которым я не пользуюсь. В stacktrace не отображаются мои собственные классы, поэтому я понятия не имею, где возникает это исключение и как его предотвратить.

Для записи: у меня есть tabhost, и на каждой вкладке есть ActivityGroup, переключающаяся между Activity.


comment
Я обнаружил, что в этом вопросе обсуждается та же проблема, но там тоже нет решения .. stackoverflow.com/questions/7469082/   -  person nhaarman    schedule 28.09.2011
comment
Хотя вы не используете FragmentManager, Honeycomb, безусловно, используется. Это происходит на настоящих планшетах Honeycomb? Или, может быть, кто-то запускает на телефоне взломанный Honeycomb или что-то в этом роде, и это та взломанная версия, которая испытывает трудности?   -  person CommonsWare    schedule 28.09.2011
comment
Я понятия не имею. Это единственная информация, которую я получаю в консоли разработчика Market, сообщение пользователя также не содержит полезной информации.   -  person nhaarman    schedule 28.09.2011
comment
Я использую Flurry, который показывает мне 11 сеансов с Android 3.0.1, и у меня есть 11 отчетов об этом исключении. Хотя могло быть совпадение. Android 3.1 и 3.2 имеют 56 и 38 сессий соответственно.   -  person nhaarman    schedule 28.09.2011
comment
В отчете об ошибках Маркета есть раздел «Платформа», иногда в нем есть версия Android-устройства.   -  person Nikolay Elenkov    schedule 28.09.2011
comment
А, понятно, наверное, я пропустил это, потому что это бесполезно .. Платформы ДРУГИЕ 11 раппортен / неделя   -  person nhaarman    schedule 28.09.2011
comment
Пожалуйста, проверьте мой ответ [здесь] [1]. Может быть полезно [1]: stackoverflow.com/questions/7469082/   -  person gunar    schedule 21.12.2012
comment
Вот мое решение: stackoverflow.com/a/31016553/3408806 Надеюсь, кто-нибудь решит эту проблему.   -  person nobjta_9x_tq    schedule 24.06.2015
comment
Лучшим рабочим ответом для меня было следующее: stackoverflow.com/a/62554599/4303296 (без ущерба для потери состояния)   -  person Beko    schedule 12.05.2021


Ответы (33)


Пожалуйста, проверьте мой ответ здесь. В основном мне просто нужно было:

@Override
protected void onSaveInstanceState(Bundle outState) {
    //No call for super(). Bug on API Level > 11.
}

Не обращайтесь к super() по методу saveInstanceState. Это напутало ...

Это известная ошибка в пакете поддержки.

Если вам нужно сохранить экземпляр и добавить что-то в свой outState Bundle, вы можете использовать следующее:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
    super.onSaveInstanceState(outState);
}

В конце концов, правильным решением было (как видно из комментариев) использовать:

transaction.commitAllowingStateLoss();

при добавлении или выполнении FragmentTransaction, вызвавшего Exception.

person Ovidiu Latcu    schedule 21.04.2012
comment
Вы должны использовать commitAllowingStateLoss () вместо commit () - person meh; 28.09.2012
comment
Этот комментарий о commitAllowingStateLoss () является самостоятельным ответом - вы должны опубликовать его именно так. - person Risadinha; 15.10.2013
comment
Что касается 'commitAllowingStateLoss' - / ›Это опасно, потому что фиксация может быть потеряна, если действие необходимо позже восстановить из своего состояния, поэтому это следует использовать только в тех случаях, когда состояние пользовательского интерфейса может неожиданно измениться на Пользователь. - person Codeversed; 17.12.2013
comment
Если я посмотрю на источник v4 для popBackStackImmediate, он сразу же выйдет из строя, если состояние было сохранено. Ранее добавление фрагмента с commitAllowingStateLoss не играет никакой роли. Мое тестирование показывает, что это правда. Это не влияет на это конкретное исключение. Нам нужен popBackStackImmediateAllowingStateLoss метод. - person Synesso; 09.01.2015
comment
@Ovidiu Глядя на источники: реализация Fragment#onSaveInstanceState - это NOP. Вы можете объяснить, почему не помогает вызов super метода? - person kiruwka; 05.02.2015
comment
Что делать с фрагментом диалога? Здесь у нас есть только вызов метода show (). - person Max; 19.02.2015
comment
@Synesso, ты прав. Ошибка вызывается popBackStackImmediate(), а не фиксацией транзакции. Вы нашли какое-нибудь решение? - person Daniele B; 08.03.2015
comment
@Synesso, может быть, есть какие-то условия, которые мы можем проверить и не вызывать popBackStackImmediate() в этих условиях? есть идеи, как обнаружить эти условия? - person Daniele B; 08.03.2015
comment
@DanieleB да, я разместил здесь ответ. Но на самом деле я нашел еще лучшее решение, используя шину сообщений Отто: зарегистрируйте фрагмент как подписчик и прослушайте асинхронный результат от шины. Отмените регистрацию при паузе и повторно зарегистрируйтесь при возобновлении. Асинхронному алгоритму также нужен метод Produce на тот момент, когда он завершается и фрагмент приостанавливается. Когда у меня будет время, я уточню свой ответ более подробно. - person Synesso; 09.03.2015
comment
@Synesso, поймать исключение - хорошее решение! в любом случае это исключение случается со мной очень редко - person Daniele B; 09.03.2015
comment
Интересно, что я использую только commitAllowingStateLoss (), пробовал оба обходных пути в onSaveInstanteState (), все равно не повезло, проблема все еще возникает время от времени. Единственное хорошее решение - это ловля исключений, грустно. - person 3c71; 25.04.2015
comment
transaction.commitAllowingStateLoss () отлично поработал! - person CoDe; 12.07.2015
comment
Кто-нибудь зарегистрировал проблему, которую я мог бы отметить? - person Binoy Babu; 20.07.2015
comment
@Ovidiu Latcu Спасибо ..transaction.commitAllowingStateLoss (); эта строчка сэкономила часы времени ..;) - person TheFlash; 15.10.2015
comment
Я думаю, что это не на 100% правильно, проверьте эту ссылку: androiddesignpatterns.com/2013/08/ - person Alberto Méndez; 27.11.2015
comment
А как насчет фрагментов диалогов? Как commitAllowingStateLoss () при отображении фрагмента в виде диалога. - person VishalKale; 25.04.2016
comment
Я смог решить эту проблему, показав фрагмент через обработчик. - person Usman; 25.09.2016
comment
вы, вероятно, вызываете fragmentTransaction::commit() из фонового потока (используя runOnUiThread или handler). Фактическая фиксация происходит немного позже. Если пользователь быстро выходит из приложения (вызывая onSavedStateInstance()), тогда эта фиксация fts фактически происходит позже; возникла эта проблема. Таким образом, решение на этом этапе, вероятно, состоит в том, чтобы разрешить потерю состояния commitAllowingStateLoss(). Пользователь все равно выходит. - person Thupten; 08.11.2016
comment
Если вы используете commitAllowingStateLoss, вы, вероятно, столкнетесь с ArrayIndexOutOfBoundsException, когда fragmentManager пытается восстановить свое состояние (не всегда). Даже если я вызвал фиксацию в onCreate(), он все равно выбросил IllegalStateException. Я до сих пор не могу понять почему. Думаю, это ошибка Android. - person Kimi Chiu; 25.11.2016
comment
Ничего себе, я не могу поверить, что это 2017 год, и предложение, опубликованное @meh, все еще работает, Android офигительно глючит - person IteratioN7T; 16.01.2017
comment
@ IteratioN7T проблема заключается в выполнении транзакций фрагмента после обратных вызовов из фоновых заданий, когда приложение не находится на переднем плане. лучший способ - избегать этого, потому что это плохая практика. - person meh; 18.01.2017
comment
Я использую commitAllowingStateLoss для приложения в магазине игр, и я все еще получаю сотни таких же исключений в этой строке: fragmentTransaction.replace (layoutId, newFragment) .commitAllowingStateLoss (); - person Serdar Samancıoğlu; 02.08.2017
comment
Что здесь transaction? - person Shailendra Madda; 11.10.2017
comment
Я получаю эту ошибку, когда не вызываю super: Метод переопределения должен вызывать super.onSaveInstanceState. Некоторые методы, такие как View # onDetachedFromWindow, требуют, чтобы вы также вызывали супер-реализацию как часть вашего метода. - person Alireza Noorali; 30.12.2017
comment
Я не использовал метод onSaveInstanceState (). Почему он разбился в одно и то же время - person Faxriddin Abdullayev; 23.01.2020

Подобное сообщение об ошибке связано с множеством проблем. Проверьте вторую строку этой конкретной трассировки стека. Это исключение конкретно связано с вызовом FragmentManagerImpl.popBackStackImmediate.

Этот вызов метода, как и popBackStack, всегда завершается ошибкой с IllegalStateException, если состояние сеанса уже было сохранено. Проверить источник. Вы ничего не можете сделать, чтобы остановить возникновение этого исключения.

  • Удаление вызова super.onSaveInstanceState не поможет.
  • Создание фрагмента с commitAllowingStateLoss не поможет.

Вот как я заметил проблему:

  • Есть форма с кнопкой отправки.
  • При нажатии кнопки создается диалоговое окно и запускается асинхронный процесс.
  • Пользователь нажимает кнопку «Домой» до завершения процесса - вызывается onSaveInstanceState.
  • Процесс завершается, выполняется обратный вызов и предпринимается попытка popBackStackImmediate.
  • IllegalStateException брошено.

Вот что я сделал, чтобы решить эту проблему:

Поскольку невозможно избежать IllegalStateException в обратном вызове, поймайте и игнорируйте его.

try {
    activity.getSupportFragmentManager().popBackStackImmediate(name);
} catch (IllegalStateException ignored) {
    // There's no way to avoid getting this if saveInstanceState has already been called.
}

Этого достаточно, чтобы приложение не вылетало. Но теперь пользователь восстановит приложение и увидит, что кнопка, которую, как он думал, нажала, вообще не была нажата (они думают). Фрагмент формы все еще отображается!

Чтобы исправить это, при создании диалогового окна установите какое-либо состояние, чтобы указать, что процесс запущен.

progressDialog.show(fragmentManager, TAG);
submitPressed = true;

И сохраните это состояние в связке.

@Override
public void onSaveInstanceState(Bundle outState) {
    ...
    outState.putBoolean(SUBMIT_PRESSED, submitPressed);
}

Не забудьте снова загрузить его в onViewCreated

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

@Override
public void onResume() {
    super.onResume();
    if (submitPressed) {
        // no need to try-catch this, because we are not in a callback
        activity.getSupportFragmentManager().popBackStackImmediate(name);
        submitPressed = false;
    }
}
person Synesso    schedule 09.01.2015
comment
Здесь интересно прочитать об этом: androiddesignpatterns.com/2013/ 08 / - person Pascal; 18.04.2016
comment
Если вы используете DialogFragment, я сделал ему альтернативу здесь: github.com/AndroidDeveloperLB/DialogShard - person android developer; 22.10.2016
comment
Что, если popBackStackImmediate был вызван самим Android? - person Kimi Chiu; 25.11.2016
comment
Абсолютно здорово. Это должен быть принятый ответ. Большое спасибо! Может быть, я бы добавил submitPressed = false; после popBackStackInmediate. - person Neonigma; 15.05.2018
comment
Я не использовал метод public void onSaveInstanceState (Bundle outState). Нужно ли мне устанавливать пустой метод для public void onSaveInstanceState (Bundle outState)? - person Faxriddin Abdullayev; 23.01.2020

Убедитесь, что активность isFinishing() перед показом фрагмента, и обратите внимание на commitAllowingStateLoss().

Пример:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}
person Naskov    schedule 04.03.2015
comment
! isFinishing () &&! isDestroyed () у меня не работает. - person Allen Vork; 06.02.2018
comment
! isFinishing () &&! isDestroyed () работал у меня, но для этого требуется API 17. Но он просто не показывает DialogFragment. См. stackoverflow.com/questions/15729138/ для других хороших решений, stackoverflow.com/ а / 41813953/2914140 мне помогли. - person CoolMind; 01.02.2019

На дворе октябрь 2017 года, и Google создает библиотеку поддержки Android с новым компонентом Lifecycle. Он предлагает новую идею для проблемы «Невозможно выполнить это действие после onSaveInstanceState».

Короче:

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

Расширенная версия с объяснением:

  • почему эта проблема вышла?

    Это потому, что вы пытаетесь использовать FragmentManager из своей активности (которая, я полагаю, будет содержать ваш фрагмент?), Чтобы совершить транзакцию для вашего фрагмента. Обычно это выглядит так, как будто вы пытаетесь выполнить какую-то транзакцию для предстоящего фрагмента, в то время как активность хоста уже вызывает метод savedInstanceState (пользователь может случайно коснуться кнопки домой, поэтому действие вызывает onStop(), в моем случае это причина)

    Обычно такой проблемы не должно происходить - мы всегда пытаемся загрузить фрагмент в действие в самом начале, например, метод onCreate() - идеальное место для этого. Но иногда это действительно происходит, особенно когда вы не можете решить, какой фрагмент загрузить в это действие, или вы пытаетесь загрузить фрагмент из AsyncTask блока (или что-то еще займет немного времени). Время до того, как транзакция фрагмента действительно произойдет, но после метода onCreate() действия пользователь может делать что угодно. Если пользователь нажмет кнопку «Домой», которая запускает onSavedInstanceState() метод действия, произойдет can not perform this action сбой.

    Если кто-то хочет глубже разобраться в этой проблеме, я предлагаю им взглянуть на этот блог сообщение. Он заглядывает глубоко внутрь слоя исходного кода и многое объясняет. Кроме того, это объясняет, почему вы не должны использовать метод commitAllowingStateLoss() для обхода этого сбоя (поверьте мне, он не предлагает ничего хорошего для вашего кода)

  • Как это исправить?

    • Следует ли использовать метод commitAllowingStateLoss() для загрузки фрагмента? Нет, не стоит;

    • Должен ли я переопределить onSaveInstanceState метод, игнорировать super метод внутри него? Нет, не стоит;

    • Должен ли я использовать волшебную isFinishing внутреннюю активность, чтобы проверить, является ли активность хоста подходящей для транзакции фрагмента? Да, это похоже верный способ.

  • Посмотрите, что может делать компонент Lifecycle.

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

    Я немного кодирую для своего собственного проекта, вот что я делаю с помощью LifeCycle. Я кодирую на Котлине.

val hostActivity: AppCompatActivity? = null // the activity to host fragments. It's value should be properly initialized.

fun dispatchFragment(frag: Fragment) {
    hostActivity?.let {
       if(it.lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)){
           showFragment(frag)
       }
    }
}

private fun showFragment(frag: Fragment) {
    hostActivity?.let {
        Transaction.begin(it, R.id.frag_container)
                .show(frag)
                .commit()
    }

Как я показал выше. Я проверю состояние жизненного цикла активности хоста. С компонентом жизненного цикла в библиотеке поддержки это могло бы быть более конкретным. Код lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED) означает, что если текущее состояние хотя бы onResume, не позже этого? Это гарантирует, что мой метод не будет выполняться в каком-то другом жизненном состоянии (например, onStop).

  • Все ли сделано?

    Конечно, нет. Код, который я показал, говорит о новом способе предотвращения сбоев приложения. Но если он перейдет в состояние onStop, эта строка кода ничего не сделает и, следовательно, ничего не покажет на вашем экране. Когда пользователи вернутся в приложение, они увидят пустой экран, это пустая активность хоста, на которой вообще нет фрагментов. Это плохой опыт (да, немного лучше, чем авария).

    Итак, я хотел бы, чтобы было что-то более приятное: приложение не выйдет из строя, если оно перейдет в жизненное состояние позже onResume, метод транзакции учитывает жизненное состояние; кроме того, действие будет пытаться продолжить выполнение этого действия транзакции фрагмента после того, как пользователь вернется в наше приложение.

    Я добавляю к этому методу еще кое-что:

class FragmentDispatcher(_host: FragmentActivity) : LifecycleObserver {
    private val hostActivity: FragmentActivity? = _host
    private val lifeCycle: Lifecycle? = _host.lifecycle
    private val profilePendingList = mutableListOf<BaseFragment>()

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun resume() {
        if (profilePendingList.isNotEmpty()) {
            showFragment(profilePendingList.last())
        }
    }

    fun dispatcherFragment(frag: BaseFragment) {
        if (lifeCycle?.currentState?.isAtLeast(Lifecycle.State.RESUMED) == true) {
            showFragment(frag)
        } else {
            profilePendingList.clear()
            profilePendingList.add(frag)
        }
    }

    private fun showFragment(frag: BaseFragment) {
        hostActivity?.let {
            Transaction.begin(it, R.id.frag_container)
                    .show(frag)
                    .commit()
        }
    }
}

Я веду список внутри этого класса dispatcher, чтобы сохранить этот фрагмент, у меня нет возможности завершить действие транзакции. И когда пользователь вернется с домашнего экрана и обнаружит, что фрагмент все еще ожидает запуска, он перейдет к методу resume() под аннотацией @OnLifecycleEvent(Lifecycle.Event.ON_RESUME). Теперь я думаю, что все должно работать так, как я ожидал.

person Anthonyeef    schedule 04.10.2017
comment
Было бы неплохо использовать Java вместо Kotlin - person Shchvova; 27.04.2018
comment
Почему ваша реализация FragmentDispatcher использует список для хранения ожидающих фрагментов, если будет восстановлен только один фрагмент? - person fraherm; 10.12.2018

Вот другое решение этой проблемы.

Используя частную переменную-член, вы можете установить возвращаемые данные как намерение, которое затем может быть обработано после super.onResume ();

Вот так:

private Intent mOnActivityResultIntent = null; 

@Override
protected void onResume() {
    super.onResume();
    if(mOnActivityResultIntent != null){
        ... do things ...
        mOnActivityResultIntent = null;
    }
 }

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
    if(data != null){
        mOnActivityResultIntent = data;
    }
}
person Jed    schedule 16.10.2012
comment
В зависимости от того, какое действие, которое вы выполняли, было запрещено, возможно, потребуется перенести его на более поздний момент, чем onResume (). Для insatcne, если проблема является FragmentTransaction.commit (), вместо этого необходимо перейти в onPostResume (). - person pjv; 25.11.2012
comment
Это для меня ответ на этот вопрос. Поскольку мне нужно было переслать полученный тег NFC предыдущему действию, это то, что мне удалось. - person Janis Peisenieks; 16.07.2013
comment
Для меня это произошло потому, что я не звонил super.onActivityResult(). - person Sufian; 10.02.2015

Краткое и рабочее решение:

Следуйте простым шагам

Шаги

Шаг 1: Заменить состояние onSaveInstanceState в соответствующем фрагменте. И убрать из него супер метод.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Шаг 2. Используйте fragmentTransaction.commitAllowingStateLoss( );

вместо fragmentTransaction.commit( ); при операциях с фрагментами.

person Vinayak    schedule 08.04.2014
comment
Ответ не копируется и не рецензируется в другом месте, где .Is был размещен в помощь людям моим рабочим решением, полученным путем нескольких проб и ошибок. - person Vinayak; 27.10.2017

ОСТОРОЖНО, использование transaction.commitAllowingStateLoss() может ухудшить восприятие пользователем. Для получения дополнительной информации о том, почему возникает это исключение, см. этот пост.

person Eric Brandwein    schedule 13.11.2015
comment
Это не дает ответа на вопрос, вы должны предоставить действительный ответ на вопрос. - person Umar Ata; 13.02.2018

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

public void onBackPressed() {}

в вашем Activity и напишите там back код. даже если такого метода нет на старых устройствах, этот метод вызывается новыми.

person saberrider    schedule 21.02.2012

Не используйте commitAllowingStateLoss (), его следует использовать только в тех случаях, когда состояние пользовательского интерфейса может неожиданно измениться для пользователя.

https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()

Если транзакция происходит в ChildFragmentManager из parentFragment, используйте для проверки снаружи parentFragment.isResume ().

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
person Chandler    schedule 19.12.2016

У меня была аналогичная проблема, сценарий был такой:

  • Моя деятельность - добавление / замена фрагментов списка.
  • Каждый фрагмент списка имеет ссылку на действие, чтобы уведомить действие при щелчке по элементу списка (шаблон наблюдателя).
  • Каждый фрагмент списка вызывает setRetainInstance (true); в своем методе onCreate.

Метод onCreate для действия был таким:

mMainFragment = (SelectionFragment) getSupportFragmentManager()
                .findFragmentByTag(MAIN_FRAGMENT_TAG);
        if (mMainFragment == null) {
            mMainFragment = new SelectionFragment();

            mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
                    R.layout.item_main_menu, getResources().getStringArray(
                            R.array.main_menu)));
mMainFragment.setOnSelectionChangedListener(this);
            FragmentTransaction transaction = getSupportFragmentManager()
                    .beginTransaction();
            transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
            transaction.commit();
        }

Возникло исключение, потому что при изменении конфигурации (ротации устройства) создается действие, основной фрагмент извлекается из истории диспетчера фрагментов, и в то же время фрагмент уже имеет ссылку OLD. к уничтоженному действию

изменение реализации на это решило проблему:

mMainFragment = (SelectionFragment) getSupportFragmentManager()
                .findFragmentByTag(MAIN_FRAGMENT_TAG);
        if (mMainFragment == null) {
            mMainFragment = new SelectionFragment();

            mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
                    R.layout.item_main_menu, getResources().getStringArray(
                            R.array.main_menu)));
            FragmentTransaction transaction = getSupportFragmentManager()
                    .beginTransaction();
            transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
            transaction.commit();
        }
        mMainFragment.setOnSelectionChangedListener(this);

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

person Mina Wissa    schedule 18.07.2015

Если вы наследуете от FragmentActivity, вы должны вызвать суперкласс в onActivityResult():

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    ...
}

Если вы этого не сделаете и попытаетесь показать диалоговое окно фрагмента в этом методе, вы можете получить OP IllegalStateException. (Честно говоря, я не совсем понимаю, почему супервызов устраняет проблему. onActivityResult() вызывается перед onResume(), поэтому ему все равно не должно быть разрешено показывать диалоговое окно фрагмента.)

person Lawrence Kesteloot    schedule 30.11.2018
comment
Хотелось бы знать, почему это решает проблему. - person Big McLargeHuge; 16.10.2019

Я получал это исключение, когда нажимал кнопку «Назад», чтобы отменить средство выбора намерений на моей активности фрагмента карты. Я решил эту проблему, заменив код onResume (где я инициализировал фрагмент) на onstart (), и приложение работает нормально. Надеюсь, это поможет.

person DCS    schedule 14.07.2014

Возможно, самое плавное и простое решение, которое я нашел в моем случае, заключалось в том, чтобы избежать выталкивания проблемного фрагмента из стека в ответ на результат активности. Итак, изменив этот вызов в моем onActivityResult():

popMyFragmentAndMoveOn();

к этому:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    public void run() {
        popMyFragmentAndMoveOn();
    }
}

помог в моем случае.

person mojuba    schedule 02.06.2017

Я думаю, что использование transaction.commitAllowingStateLoss(); - не лучшее решение. Это исключение будет выдано, когда конфигурация действия изменится и будет вызван фрагмент onSavedInstanceState(), и после этого ваш асинхронный метод обратного вызова попытается зафиксировать фрагмент.

Простым решением может быть проверка, изменяет ли активность конфигурацию или нет.

например проверить isChangingConfigurations()

i.e.

if(!isChangingConfigurations()) { //commit transaction. }

Оформить заказ также по этой ссылке.

person Amol Desai    schedule 13.08.2015
comment
Каким-то образом я получил это исключение, когда пользователь что-то нажимает (щелчок является триггером для совершения транзакции-фиксации). Как это могло произойти? Ваше решение здесь? - person android developer; 21.09.2015
comment
@androiddeveloper, что еще вы делаете при нажатии пользователем. каким-то образом фрагмент сохраняет свое состояние до того, как вы зафиксируете транзакцию - person Amol Desai; 23.09.2015
comment
Исключение возникло в точной строке фиксации транзакции. Кроме того, у меня была странная опечатка: вместо слова здесь я имел в виду работу здесь. - person android developer; 23.09.2015
comment
@androiddeveloper, вы правы! но перед совершением транзакции вы создаете какой-либо фоновый поток или что-то в этом роде? - person Amol Desai; 24.09.2015
comment
Я так не думаю (извините, меня нет в офисе), но какое это имеет значение? Здесь все элементы пользовательского интерфейса ... Если я когда-нибудь сделаю что-то в фоновом потоке, у меня будут исключения, плюс я не помещаю элементы, связанные с пользовательским интерфейсом, в фоновые потоки, так как это слишком рискованно. - person android developer; 24.09.2015

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

Вы можете использовать transaction.commitAllowingStateLoss () вместо transaction.commit () для загрузки фрагмента.

or

Создайте логическое значение и проверьте, не будет ли активность приостанавливаться

@Override
public void onResume() {
    super.onResume();
    mIsResumed = true;
}

@Override
public void onPause() {
    mIsResumed = false;
    super.onPause();
}

затем при загрузке проверки фрагмента

if(mIsResumed){
//load the your fragment
}
person Sharath kumar    schedule 13.09.2017

Если вы выполняете FragmentTransaction в onActivityResult, что вы можете сделать, вы можете установить какое-то логическое значение внутри onActivityResult, тогда в onResume вы можете выполнить свою FragmentTransaction на основе логического значения. Пожалуйста, обратитесь к приведенному ниже коду.

@Override
protected void onResume() {
    super.onResume;
    if(isSwitchFragment){
        isSwitchFragment=false;
        bottomNavigationView.getTabAt(POS_FEED).select();
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == FilterActivity.FILTER_REQUEST_EVENT && data != null) {
        isSwitchFragment=true;
    }
}
person anoopbryan2    schedule 11.04.2018
comment
Пожалуйста, не размещайте код как изображение, вместо этого используйте форматирование кода. - person Micer; 11.04.2018
comment
Да, это помогло мне .., Это точное и правильное решение для устройства с зефиром. Я получил это исключение и решил с помощью этого простого трюка .. !! Следовательно, проголосовали "за". - person sandhya sasane; 16.09.2018

Предоставлено: Решение для исключения IllegalStateException

Эта проблема раздражала меня долгое время, но, к счастью, я нашел для нее конкретное решение. Подробное объяснение этого здесь..

Использование commitAllowStateloss () может предотвратить это исключение, но приведет к сбоям в пользовательском интерфейсе. До сих пор мы понимали, что IllegalStateException встречается, когда мы пытаемся зафиксировать фрагмент после потери состояния Activity, поэтому мы должны просто отложить транзакцию до восстановления состояния . Это можно просто сделать так

Объявить две приватные логические переменные

 public class MainActivity extends AppCompatActivity {

    //Boolean variable to mark if the transaction is safe
    private boolean isTransactionSafe;

    //Boolean variable to mark if there is any transaction pending
    private boolean isTransactionPending;

Теперь в onPostResume () и onPause мы устанавливаем и отключаем нашу логическую переменную isTransactionSafe. Идея состоит в том, чтобы пометить транзакцию как безопасную только тогда, когда действие находится на переднем плане, чтобы не было возможности потери состояния.

/*
onPostResume is called only when the activity's state is completely restored. In this we will
set our boolean variable to true. Indicating that transaction is safe now
 */
public void onPostResume(){
    super.onPostResume();
    isTransactionSafe=true;
}
/*
onPause is called just before the activity moves to background and also before onSaveInstanceState. In this
we will mark the transaction as unsafe
 */

public void onPause(){
    super.onPause();
    isTransactionSafe=false;

}

private void commitFragment(){
    if(isTransactionSafe) {
        MyFragment myFragment = new MyFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.frame, myFragment);
        fragmentTransaction.commit();
    }
}

-То, что мы сделали до сих пор, спасет от IllegalStateException, но наши транзакции будут потеряны, если они будут выполнены после того, как действие перейдет в фоновый режим, вроде commitAllowStateloss (). Чтобы помочь с этим, у нас есть логическая переменная isTransactionPending

public void onPostResume(){
   super.onPostResume();
   isTransactionSafe=true;
/* Here after the activity is restored we check if there is any transaction pending from
the last restoration
*/
   if (isTransactionPending) {
      commitFragment();
   }
}


private void commitFragment(){

 if(isTransactionSafe) {
     MyFragment myFragment = new MyFragment();
     FragmentManager fragmentManager = getFragmentManager();
     FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
     fragmentTransaction.add(R.id.frame, myFragment);
     fragmentTransaction.commit();
     isTransactionPending=false;
 }else {
     /*
     If any transaction is not done because the activity is in background. We set the
     isTransactionPending variable to true so that we can pick this up when we come back to
foreground
     */
     isTransactionPending=true;
 }
}
person IrshadKumail    schedule 30.06.2018

Что касается отличного ответа @Anthonyeef, вот пример кода на Java:

private boolean shouldShowFragmentInOnResume;

private void someMethodThatShowsTheFragment() {

    if (this.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
        showFragment();
    } else {
        shouldShowFragmentInOnResume = true;
    }
}

private void showFragment() {
    //Your code here
}

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

    if (shouldShowFragmentInOnResume) {
        shouldShowFragmentInOnResume = false;
        showFragment();
    }
}
person MorZa    schedule 17.07.2018

Исключение выбрасывается здесь (в FragmentActivity):

@Override
public void onBackPressed() {
    if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
        super.onBackPressed();
    }
}

В _2 _ , _ 3_ вызывается в первую очередь. Это причина IllegalStateException. См. Реализацию ниже:

private void checkStateLoss() {
    if (mStateSaved) { // Boom!
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
    if (mNoTransactionsBecause != null) {
        throw new IllegalStateException(
                "Can not perform this action inside of " + mNoTransactionsBecause);
    }
}

Я решаю эту проблему, просто используя флаг, чтобы отметить текущий статус Activity. Вот мое решение:

public class MainActivity extends AppCompatActivity {
    /**
     * A flag that marks whether current Activity has saved its instance state
     */
    private boolean mHasSaveInstanceState;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

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

    @Override
    protected void onResume() {
        super.onResume();
        mHasSaveInstanceState = false;
    }

    @Override
    public void onBackPressed() {
        if (!mHasSaveInstanceState) {
            // avoid FragmentManager.checkStateLoss()'s throwing IllegalStateException
            super.onBackPressed();
        }
    }
}
person Frost Lau    schedule 07.08.2017

Если у вас произошел сбой при использовании метода popBackStack () или popBackStackImmediate (), попробуйте исправить это с помощью:

        if (!fragmentManager.isStateSaved()) {
            fragmentManager.popBackStackImmediate();
        }

Это сработало и для меня.

person Tapa Save    schedule 12.12.2018
comment
Обратите внимание, что для этого требуется API 26 и выше. - person Itay Feldman; 31.12.2019

В моем случае я получил эту ошибку в методе переопределения onActivityResult. Покопавшись, я понял, что, может быть, мне нужно было раньше называть "super".
Я добавил это, и это сработало

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data); //<--- THIS IS THE SUPPER CALL
    if (resultCode == Activity.RESULT_OK && requestCode == 0) {
        mostrarFragment(FiltroFragment.newInstance())
    }

}

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

person jan4co    schedule 24.01.2019

Фрагментные транзакции не должны выполняться после Activity.onStop()! Убедитесь, что у вас нет обратных вызовов, которые могли бы выполнить транзакцию после onStop(). Лучше устранить причину, чем пытаться обходить проблему подходами типа .commitAllowingStateLoss()

person Ivo Stoyanov    schedule 21.06.2019

Начиная с версии библиотеки поддержки 24.0.0, вы можете вызывать метод FragmentTransaction.commitNow(), который синхронно фиксирует эту транзакцию, вместо вызова commit(), за которым следует executePendingTransactions(). Как говорится в документации, этот подход даже лучше :

Вызов commitNow предпочтительнее, чем вызов commit (), за которым следует executePendingTransactions (), поскольку у последнего будет побочный эффект попытки зафиксировать все текущие ожидающие транзакции, независимо от того, является ли это желаемым поведением или нет.

person Volodymyr    schedule 29.08.2016

Я знаю, что есть принятый ответ от @Ovidiu Latcu, но через некоторое время ошибка все еще сохраняется.

@Override
protected void onSaveInstanceState(Bundle outState) {
     //No call for super(). Bug on API Level > 11.
}

Crashlytics все еще присылает мне это странное сообщение об ошибке.

Однако ошибка теперь возникает только в версии 7+ (Nougat). Мое исправление заключалось в использовании commitAllowingStateLoss () вместо commit () в fragmentTransaction.

Этот пост полезен для commitAllowingStateLoss () и никогда больше не сталкивался с проблемой фрагмента .

Подводя итог, принятый здесь ответ может работать на версиях Android до Nougat.

Это может сэкономить кому-то несколько часов поиска. счастливые кодировки. ‹3 ура

person ralphgabb    schedule 18.05.2018

Чтобы обойти эту проблему, мы можем использовать компонент архитектуры навигации, который был представлен в Google I / O 2018. Компонент архитектуры навигации упрощает реализацию навигации в приложении Android.

person Levon Petrosyan    schedule 22.06.2018
comment
Это недостаточно хорошо и глючно (плохая обработка глубоких ссылок, не удается сохранить фрагменты отображения / скрытия состояния и некоторые важные проблемы, которые все еще открыты) - person hzandi; 23.12.2019

Расширение Kotlin

fun FragmentManager?.replaceAndAddToBackStack(
    @IdRes containerViewId: Int,
    fragment: () -> Fragment,
    tag: String
) {
    // Find and synchronously remove a fragment with the same tag.
    // The second transaction must start after the first has finished.
    this?.findFragmentByTag(tag)?.let {
        beginTransaction().remove(it).commitNow()
    }
    // Add a fragment.
    this?.beginTransaction()?.run {
        replace(containerViewId, fragment, tag)
        // The next line will add the fragment to a back stack.
        // Remove if not needed.
        // You can use null instead of tag, but tag is needed for popBackStack(), 
        // see https://stackoverflow.com/a/59158254/2914140
        addToBackStack(tag)
    }?.commitAllowingStateLoss()
}

Использование:

val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)
person CoolMind    schedule 23.07.2019
comment
Вы можете удалить () - ›перед фрагментом - person Claire; 05.03.2020
comment
@ Клэр, спасибо! Вы имеете в виду переход на fragment: Fragment? Да, я пробовал этот вариант, но в этом случае фрагмент создавался бы во всех случаях (даже если fragmentManager == null, но я с такой ситуацией не сталкивался). Я обновил ответ и заменил null на tag в addToBackStack(). - person CoolMind; 05.03.2020

Добавьте это в свою деятельность

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (outState.isEmpty()) {
        // Work-around for a pre-Android 4.2 bug
        outState.putBoolean("bug:fix", true);
    }
}
person yifan    schedule 03.03.2016

Я также столкнулся с этой проблемой, и проблема возникает каждый раз, когда изменяется контекст вашего FragmentActivity (например, изменяется ориентация экрана и т. Д.). Поэтому лучшее решение - обновить контекст из вашего FragmentActivity.

person Adam    schedule 01.04.2016

В итоге я создал базовый фрагмент и заставил все фрагменты в моем приложении расширить его.

public class BaseFragment extends Fragment {

    private boolean mStateSaved;

    @CallSuper
    @Override
    public void onSaveInstanceState(Bundle outState) {
        mStateSaved = true;
        super.onSaveInstanceState(outState);
    }

    /**
     * Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
     * would otherwise occur.
     */
    public void showAllowingStateLoss(FragmentManager manager, String tag) {
        // API 26 added this convenient method
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (manager.isStateSaved()) {
                return;
            }
        }

        if (mStateSaved) {
            return;
        }

        show(manager, tag);
    }
}

Затем, когда я пытаюсь показать фрагмент, я использую showAllowingStateLoss вместо show

нравится:

MyFragment.newInstance()
.showAllowingStateLoss(getFragmentManager(), MY_FRAGMENT.TAG);

Я пришел к этому решению из этого PR: https://github.com/googlesamples/easypermissions/pull/170/files

person Ahmad Melegy    schedule 15.12.2017

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

@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();
        }
    }
}
person android developer    schedule 09.02.2018

У меня была точно такая же проблема. Произошло это из-за разрушения предыдущей активности. когда я поддерживал предыдущее действие, оно было уничтожено. Я поставил это базовое действие (НЕПРАВИЛЬНО)

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    SpinnerCustom2.setFragmentManager(getSupportFragmentManager());
    onCreateDrawerActivity(savedInstanceState);
}

Я положил это в onStart, это было ПРАВИЛЬНО

@Override
protected void onStart() {
    super.onStart();
    SpinnerCustom2.setFragmentManager(getSupportFragmentManager());

}
person Samet ÖZTOPRAK    schedule 13.06.2018

@Gian Gomen В моем случае вызов SUPER решает проблему. Кажется более правильным решением, чем commitAllowingStateLoss (), потому что он решает проблему, а не скрывает ее.

@Override
public void onRequestPermissionsResult(
     final int requestCode,
     @NonNull final String[] permissions, 
     @NonNull final int[] grantResults
) {
        super.onRequestPermissionsResult(requestCode,permissions, grantResults); //<--- Without this line crash 
        switch (requestCode) {
            case Constants.REQUEST_CODE_PERMISSION_STORAGE:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    onPermissionGranted(Constants.REQUEST_CODE_PERMISSION_STORAGE);
                }
                break;
        }

person no_cola    schedule 13.02.2019

используйте remove () вместо popup (), если состояние сохранено.

   private void removeFragment() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    if (fragmentManager.isStateSaved()) {
        List<Fragment> fragments = fragmentManager.getFragments();
        if (fragments != null && !fragments.isEmpty()) {
            fragmentManager.beginTransaction().remove(fragments.get(fragments.size() - 1)).commitAllowingStateLoss();
        }
    }
}
person kim    schedule 07.03.2019