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

496

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

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

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

nhaarman
источник
2
Я нашел этот вопрос обсуждали один и тот же вопрос, но нет решения там либо .. stackoverflow.com/questions/7469082/...
nhaarman
3
Пока вы не используете FragmentManager, Honeycomb, безусловно, есть. Это происходит на настоящих сотовых планшетах? Или, может быть, кто-то запускает взломанный Honeycomb на телефоне или что-то в этом роде, и у этого взломанного издания возникают трудности?
CommonsWare
1
Не имею представления. Это единственная информация, которую я получаю в консоли разработчика рынка, пользовательское сообщение также не содержит никакой полезной информации ..
nhaarman
Я использую Flurry, которая показывает мне 11 сеансов с Android 3.0.1, и у меня есть 11 отчетов об этом исключении. Может быть совпадение, хотя. Android 3.1 и 3.2 имеют 56 и 38 сессий соответственно.
nhaarman
В отчете об ошибках в Маркете есть раздел «Платформа», иногда в нем есть версия устройства для Android.
Николай Еленков

Ответы:

720

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

@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.

Овидиу Латку
источник
370
Вы должны использовать commitAllowingStateLoss () вместо того , чтобы совершить ()
Мех
18
Этот комментарий о commitAllowingStateLoss () сам по себе является ответом - вы должны опубликовать его так.
Risadinha
20
Относительно 'commitAllowingStateLoss' - /> "Это опасно, потому что фиксация может быть потеряна, если впоследствии необходимо восстановить активность из ее состояния, поэтому это следует использовать только в тех случаях, когда нормально изменить состояние пользовательского интерфейса при Пользователь."
Codeversed
10
Если я смотрю на источник v4 для popBackStackImmediateнего сразу же не удается, если состояние было сохранено. Предварительное добавление фрагмента с помощью commitAllowingStateLossне играет никакой роли. Мое тестирование показывает, что это правда. Это не влияет на это конкретное исключение. Нам нужен popBackStackImmediateAllowingStateLossметод.
Synesso
3
@DanieleB да, я отправил ответ здесь. Но на самом деле я нашел еще лучшее решение, используя шину сообщений Otto: зарегистрируйте фрагмент в качестве подписчика и прослушайте асинхронный результат от шины. Отмените регистрацию на паузе и повторно зарегистрируйтесь на резюме. Асинхронный режим также нуждается в методе Produce для случаев, когда он завершается и фрагмент приостанавливается. Когда у меня будет время, я уточню свой ответ с этим более подробно.
Synesso
130

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

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

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

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

  • Есть форма с кнопкой отправки.
  • При нажатии кнопки создается диалоговое окно и запускается асинхронный процесс.
  • Пользователь нажимает клавишу home до завершения процесса - 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;
    }
}
Synesso
источник
5
Интересное чтение об этом здесь: androiddesignpatterns.com/2013/08/…
Паскаль
Если вы используете DialogFragment, я сделал альтернативу этому здесь: github.com/AndroidDeveloperLB/DialogShard
разработчик Android
Что делать, если popBackStackImmediateAndroid вызвал сам?
Кими Чиу
1
Абсолютно здорово. Этот должен быть принятым ответом. Большое спасибо! Может быть, я бы добавил submitPressed = false; после popBackStackInmediate.
Неонигма
Я не использовал публичный метод void onSaveInstanceState (Bundle outState). Нужно ли устанавливать пустой метод для public void onSaveInstanceState (Bundle outState)?
Факсриддин Абдуллаев
55

Проверьте, если активность, 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();
}
Naskov
источник
1
! isFinishing () &&! isDestroyed () не работает для меня.
Аллен Ворк
! isFinishing () &&! isDestroyed () работал для меня, но для этого требуется API 17. Но он просто не показывает a DialogFragment. См. Stackoverflow.com/questions/15729138/… для других хороших решений, stackoverflow.com/a/41813953/2914140 помог мне.
CoolMind
29

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

Короче говоря:

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

Более длинная версия с объяснением:

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

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

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

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

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

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

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

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

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

    По сути, 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)аннотацией. Теперь я думаю, что это должно работать так, как я ожидал.

Anthonyeef
источник
8
Было бы неплохо использовать Java вместо Котлина
Щвова
1
Почему ваша реализация FragmentDispatcherиспользует список для хранения ожидающих фрагментов, если будет восстановлен только один фрагмент?
fraherm
21

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

Используя закрытую переменную-член, вы можете установить возвращаемые данные как намерение, которое затем может быть обработано после 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;
    }
}
Джед
источник
7
В зависимости от того, какое действие вы выполняли, что было запрещено, может потребоваться перейти на более поздний момент, чем onResume (). Для insatcne, если FragmentTransaction.commit () является проблемой, вместо этого необходимо перейти к onPostResume ().
PJV
1
Это ответ на этот вопрос для меня. Так как мне нужно было переслать полученный тег NFC к предыдущему действию, это то, что он сделал для меня.
Янис Пейсениекс
7
Для меня это происходило, потому что я не звонил super.onActivityResult().
Суфий
20

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

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

меры

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

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

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

а не fragmentTransaction.commit( ); пока фрагментные операции.

Винаяк
источник
Ответ не копируется или не рецензируется в другом месте, где он был опубликован, чтобы помочь людям с помощью моего рабочего решения, полученного несколькими
Vinayak
12

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

Эрик Брандвейн
источник
6
Это не дает ответа на вопрос, вы должны предоставить правильный ответ на вопрос
Умар Ата
10

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

public void onBackPressed() {}

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

saberrider
источник
6

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

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

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

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
лавочник
источник
5

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

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

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

Мина Сэми
источник
5

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

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

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

Лоуренс Кестулут
источник
1
Хотелось бы знать, почему это решает проблему.
Big McLargeHuge
3

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

DCS
источник
2

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

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

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

т.е.

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

Проверьте эту ссылку, а также

Амол Десаи
источник
Каким-то образом я получил это исключение, когда пользователь нажимает на что-то (щелчок - это триггер для совершения транзакции). Как это могло произойти? Будет ли ваше решение здесь?
разработчик Android
@androiddeveloper что еще вы делаете по клику пользователя. каким-то образом фрагмент сохраняет свое состояние, прежде чем совершить транзакцию
Amol Desai
Исключение было сгенерировано в точной строке фиксации транзакции. Кроме того, у меня была странная опечатка: вместо «здесь здесь» я имел в виду «работать здесь».
Android-разработчик
@androiddeveloper вы правы! но перед совершением транзакции вы создаете какой-либо фоновый поток или что-то?
Амол Десаи
Я так не думаю (извините, я вне офиса), но почему это имеет значение? Здесь все дело в пользовательском интерфейсе ... Если я когда-нибудь сделаю что-то в фоновом потоке, у меня будут исключения, плюс я не помещаю вещи, связанные с пользовательским интерфейсом, в фоновые потоки, поскольку это слишком рискованно.
Android-разработчик
2

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

popMyFragmentAndMoveOn();

к этому:

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

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

mojuba
источник
2

Если вы выполняете некоторую 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;
    }
}
anoopbryan2
источник
Пожалуйста, не размещайте код в качестве изображения, используйте форматирование кода.
Майкер
1
Да, это помогло мне., Это точное и правильное решение для устройства зефира, я получил это исключение и решил с помощью этого простого трюка .. !! Отсюда и проголосовали.
сандхья сасане
2

Вежливость: решение для 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;
 }
}
IrshadKumail
источник
2

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

Иво Стоянов
источник
1

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

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

Владимир
источник
1

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

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

или

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

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

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

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

if(mIsResumed){
//load the your fragment
}
Шарат Кумар
источник
1

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

Левон Петросян
источник
Это не достаточно хорошо и глючит (плохая обработка глубоких ссылок, невозможно сохранить состояние показать / скрыть фрагменты и некоторые важные проблемы, которые все еще открыты)
do01
1

Что касается @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();
    }
}
Morza
источник
1

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

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

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

Тапа сохранить
источник
Обратите внимание, что это требует API 26 и выше
Итай Фельдман
1

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

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())
    }

}

Может быть, вам просто нужно добавить super в любое переопределение, которое вы делаете перед вашим кодом.

Джан Гомен
источник
1

Удлинение Котлина

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)
CoolMind
источник
Вы можете удалить () -> перед фрагментом
Клэр
@ Клэр, спасибо! Вы имеете в виду переход на fragment: Fragment? Да, я попробовал этот вариант, но в этом случае фрагмент будет создан во всех случаях (даже когда фрагментManager == ноль, но я не сталкивался с этой ситуацией). Я обновил ответ и изменил значение null на тег addToBackStack().
CoolMind
1

Этот сбой происходит из-за того, что FragmentTransaction совершается после того, как жизненный цикл его собственной активности уже запущен наSaveInstanceState. Это часто вызывается фиксацией FragmentTransactions из асинхронного обратного вызова. Проверьте связанный ресурс для более подробной информации.

Фрагмент транзакций и активность State Loss

http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html

Болендра Сингх
источник
0

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

@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);
    }
}
Ифань
источник
0

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

Адам
источник
0

Я закончил с созданием базового фрагмента и сделать все фрагменты в моем приложении расширить его

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

Ахмад Эль-Мелеги
источник
0

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

@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();
        }
    }
}
разработчик Android
источник
0

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

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

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

Однако теперь ошибка возникает только в версии 7+ (Nougat). Мое исправление состояло в том, чтобы использовать commitAllowingStateLoss () вместо commit () для фрагмента транзакции .

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

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

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

ralphgabb
источник