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, яким я не користуюся. У стек-трек не відображається жоден із моїх власних занять, тому я не маю уявлення, де відбувається це виняток і як його запобігти.

Для запису: у мене є табло, і в кожній вкладці є перемикання між групами діяльності.


2
Я знайшов це питання обговорювали один і той же питання, але немає рішення там або .. stackoverflow.com/questions/7469082 / ...
nhaarman

3
Поки ви не використовуєте FragmentManager, 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" - /> "Це небезпечно, оскільки фіксація може бути втрачена, якщо діяльність потрібно буде згодом відновити зі свого стану, тому це слід використовувати лише у випадках, коли стан UI несподівано зміниться на користувач. "
Зашифрований

10
Якщо я дивлюсь на джерело v4, popBackStackImmediateто він негайно виходить з ладу, якщо стан було збережено. Раніше додавання фрагмента за допомогою commitAllowingStateLossне грає жодної ролі. Моє тестування показує, що це правда. Це не впливає на цей конкретний виняток. Нам потрібен popBackStackImmediateAllowingStateLossметод.
Синесо

3
@DanieleB так, я опублікував відповідь тут. Але я фактично знайшов ще краще рішення, використовуючи шину повідомлень Otto: зареєструйте фрагмент як підписник і прослухайте результат асинхронізації з шини. Скасуйте реєстрацію на паузі та перереєструйтесь на резюме. Асинхрон також потребує методу Produce для тих часів, коли він завершується, і фрагмент призупиняється. Коли я знайду час, я оновлю відповідь на це більш детально.
Синесо

130

Існує багато пов'язаних проблем з аналогічним повідомленням про помилку. Перевірте другий рядок конкретного сліду стека. Цей виняток спеціально пов'язаний із закликом до 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;
    }
}

5
Цікаве про це читайте тут: androiddesignpatterns.com/2013/08/…
Паскаль

Якщо ви використовуєте DialogFragment, я зробив альтернативу йому тут: github.com/AndroidDeveloperLB/DialogShard
розробник для android

Що робити, якщо popBackStackImmediateтелефонував сам Android?
Кімі Чіу

1
Абсолютно чудово. Це має бути прийнятою відповіддю. Дуже дякую! Можливо, я б додав submitPress = false; після popBackStackInmediate.
Неонігма

Я не використовував публічний недійсний метод onSaveInstanceState (Bundle outState). Чи потрібно встановити порожній метод для публічної недійсності 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();
}

1
! isFinishing () &&! isDestroyed () не працює для мене.
Аллен Ворк

! isFinishing () &&! isDestroyed () працював для мене, але для нього потрібен API 17. Але він просто не показує DialogFragment. Дивіться stackoverflow.com/questions/15729138/… про інші хороші рішення, stackoverflow.com/a/41813953/2914140 мені допоміг.
CoolMind

29

Настав жовтень 2017 року, і Google робить Бібліотеку підтримки Android з новими компонентами, що називають компонентами життєвого циклу. Він надає нову ідею для цієї проблеми "Неможливо виконати цю дію після проблеми 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)анотацією. Зараз я думаю, що це має працювати так, як я очікував.


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 до попередньої діяльності, саме це зробило для мене.
Janis Peisenieks

7
Для мене це відбувалося тому, що я не дзвонив super.onActivityResult().
Суфіан

20

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

Дотримуйтесь простих кроків

Кроки

Крок 1: Переосмислення onSaveInstanceStateстану у відповідному фрагменті. І видаліть із нього супер метод.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Крок 2: Використовуйте fragmentTransaction.commitAllowingStateLoss( );

замість операцій з fragmentTransaction.commit( ); фрагментами.


Відповідь не скопіюється та не рецензується в іншому випадку, де. Був розміщений, щоб допомогти людям моїм робочим рішенням, яке отримано декількома спробами та помилками
Vinayak

12

ПОВЕРНІТЬСЯ , використання цього transaction.commitAllowingStateLoss()може спричинити поганий досвід для користувача. Більш детальну інформацію про те, чому цей виняток кидається, дивіться у цій публікації .


6
Це не дає відповіді на питання, ви повинні надати дійсну відповідь на питання
Umar Ata

10

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

public void onBackPressed() {}

у вашому Activityі зробіть там якийсь backкод. навіть якщо такого методу на старих пристроях немає, цей метод називається новішими.


6

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

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

Якщо транзакція відбувається у ChildFragmentManager з батьківськогоFragment, використовуйте назовні 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();
        }

Виняток було закинуто, тому що при зміні конфігурації (пристрій повертається), створюється активність, основний фрагмент витягується з історії менеджера фрагментів, і в той же час фрагмент вже має СТАРО посилання на знищену діяльність

змінивши реалізацію на цю проблему, вирішили:

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

Якщо цього не зробити і спробувати показати діалогове вікно фрагмента в цьому методі, ви можете отримати ОП IllegalStateException. (Якщо чесно, я не зовсім розумію, чому супервиклик виправляє проблему. onActivityResult()Викликається раніше onResume(), тому все одно не можна дозволяти показувати діалогове вікно фрагмента.)


1
Хочеться дізнатися, чому це вирішує проблему.
Big McLargeHuge

3

Цей виняток я отримував, коли натискав кнопку "назад", щоб скасувати вибір вибору наміру на моєму фрагменті. Я вирішив це, замінивши код onResume (де я ініціалізував фрагмент) на onstart (), і додаток працює нормально. Сподіваюся, це допомагає.


2

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

Простим рішенням може бути перевірка, чи змінюється активність конфігурації чи ні

наприклад, перевірити isChangingConfigurations()

тобто

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

Ознайомтесь і з цим посиланням


Я якось отримав цей виняток, коли користувач щось клацає (натискання є тригером для здійснення транзакції-фіксації). Як це могло бути? Чи було б тут ваше рішення?
андроїд розробник

@androiddeveloper що ще ти робиш при натисканні користувача. якийсь фрагмент зберігає свій стан, перш ніж здійснити транзакцію
Amol Desai

Виняток було викинуто на точну лінію транзакції. Також у мене був дивний друкарський помилок: замість "тут тут" я мав на увазі "тут працюй".
андроїд розробник

@androiddeveloper ви праві! але перед здійсненням транзакції ви нерестуєте якусь фонову нитку чи щось таке?
Amol Desai

Я так не думаю (вибачте, що я поза офісом), але навіщо це мати значення? Тут все інтерфейс користувача ... Якщо я колись щось роблю на фоновій темі, я мав би там винятки, плюс я не ставлю пов'язані з інтерфейсом речі на фонові нитки, оскільки це занадто ризиковано.
андроїд розробник

2

Можливо, найгладшим і найпростішим рішенням, яке я знайшов у моєму випадку, було уникнути вискочення ображеного фрагмента зі стека у відповідь на результат діяльності. Тож зміна цього дзвінка в моєму onActivityResult():

popMyFragmentAndMoveOn();

до цього:

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

допомогли в моєму випадку.


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

Будь ласка, не публікуйте код як зображення, використовуйте натомість форматування коду.
Micer

1
Так, це допомогло мені .., Це саме і правильне рішення для пристрою зефіру я отримав цей виняток і вирішив цим простим трюком .. !! Звідси проголосували.
sandhya sasane

2

Люб’язно: рішення для IllegalStateException

Це питання мене дратувало багато часу, але, на щастя, я прийшов до конкретного рішення. Детальне пояснення це тут .

Використання commitAllowStateloss () може запобігти цьому винятку, але призведе до нерівностей у користувальницькому інтерфейсі. Поки ми зрозуміли, що IllegalStateException виникає, коли ми намагаємося зробити фрагмент після втрати стану активності, тому нам слід просто відкласти транзакцію, поки стан не відновиться . Це можна зробити просто так

Оголосити дві приватні булеві змінні

 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, але наші транзакції будуть втрачені, якщо вони будуть виконані після того, як активність перейде на другий план, на зразок набуття комітетуAllowStateloss (). Щоб допомогти у цьому, у нас є булева змінна TransTractionPending

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

2

Операції з фрагментами не слід виконувати після Activity.onStop()! Перевірте, чи немає у вас зворотних дзвінків, які могли б виконати транзакцію після onStop(). Краще виправити причину, а не намагатися обійти проблему подібними підходами.commitAllowingStateLoss()


1

Починаючи з бібліотеки підтримки версії 24.0.0, ви можете викликати FragmentTransaction.commitNow()метод виклику, який здійснює цю транзакцію синхронно, а не виклик, commit()за яким слід executePendingTransactions(). Як свідчить документація, цей підхід ще краще:

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


1

Кожного разу, коли ви намагаєтеся завантажити фрагмент у своїй діяльності, переконайтеся, що активність відновлена, і не збираєтесь призупиняти стан. У стані паузи ви можете втратити операцію фіксації.

Для завантаження фрагмента ви можете використовуватиaction.commitAllowingStateLoss () замість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.


Це недостатньо добре, і bugy (поганий
повод з глибоким

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

1

Якщо у вас є збій з popBackStack () або popBackStackImmediate () метод, будь ласка, спробуйте встановити з:

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

Це працює і для мене.


Зауважте, що для нього потрібен API 26 і вище
Itay Feldman

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

}

Можливо, вам просто потрібно додати "супер" для будь-якого переопрацювання, яке ви робите перед кодом.


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)

Ви можете видалити () -> перед фрагментом
Клер

@Claire, дякую! Ви маєте на увазі перехід до fragment: Fragment? Так, я спробував цей варіант, але в цьому випадку фрагмент буде створений у всіх випадках (навіть коли fragmentManager == null, але з цією ситуацією я не стикався). Я оновив відповідь і змінив нульове позначення addToBackStack().
CoolMind

1

Цей збій відбувається через вчинення фрагментної операції після того, як життєвий цикл власної діяльності вже запущений наSaveInstanceState. Це часто викликано вчиненням фрагментних операцій з асинхронного зворотного виклику. Перегляньте пов'язаний ресурс для отримання більш детальної інформації.

Операції з фрагментами та втрата стану активності

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

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 ура

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.