IllegalStateException: Nie można wykonać tej akcji po onSaveInstanceState z ViewPager

496

Otrzymuję raporty użytkowników z mojej aplikacji na rynku, podając następujący wyjątek:

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)

Najwyraźniej ma to coś wspólnego z FragmentManager, którego nie używam. Stacktrace nie pokazuje żadnej z moich własnych klas, więc nie mam pojęcia, gdzie występuje ten wyjątek i jak temu zapobiec.

Dla przypomnienia: mam tabhost, aw każdej zakładce jest ActivityGroup przełączająca się między działaniami.

nhaarman
źródło
2
Znalazłem to pytanie omawiające ten sam problem, ale tam też nie ma rozwiązania .. stackoverflow.com/questions/7469082/…
nhaarman
3
Podczas gdy nie używasz FragmentManager, plaster miodu z pewnością tak jest. Czy dzieje się tak na prawdziwych tabletach o strukturze plastra miodu? A może ktoś używa zhakowanego plastra miodu na telefonie lub czymś innym, a ta zhakowana edycja ma trudności?
CommonsWare
1
Nie mam pojęcia. To jedyne informacje, które otrzymuję w Konsoli programisty rynku, wiadomość użytkownika również nie zawiera żadnych przydatnych informacji ..
nhaarman
Używam Flurry, która pokazuje mi 11 sesji z Androidem 3.0.1 i mam 11 raportów o tym wyjątku. Może to być zbieg okoliczności. Android 3.1 i 3.2 mają odpowiednio 56 i 38 sesji.
nhaarman
Raport o błędach rynkowych zawiera sekcję „Platforma”, czasem zawiera wersję urządzenia z Androidem.
Nikolay Elenkov

Odpowiedzi:

720

Sprawdź moją odpowiedź tutaj . Zasadniczo musiałem po prostu:

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

Nie dzwoń super()na numersaveInstanceState metody. To wszystko zepsuło ...

Jest to znany błąd w pakiecie wsparcia.

Jeśli chcesz zapisać instancję i dodać coś do swojego outState Bundle, możesz użyć następujących opcji:

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

Ostatecznie właściwym rozwiązaniem było (jak widać w komentarzach) użycie:

transaction.commitAllowingStateLoss();

podczas dodawania lub wykonywania tego, FragmentTransactionktóry był przyczyną Exception.

Ovidiu Latcu
źródło
370
Powinieneś użyć commitAllowingStateLoss () zamiast commit ()
Meh
18
Ten komentarz na temat commitAllowingStateLoss () jest odpowiedzią samą w sobie - powinieneś to tak napisać.
Risadinha
20
W odniesieniu do „commitAllowingStateLoss” - /> „Jest to niebezpieczne, ponieważ zatwierdzenie może zostać utracone, jeśli działanie będzie musiało zostać później przywrócone z jego stanu, więc powinno się go używać tylko w przypadkach, w których stan interfejsu użytkownika może nieoczekiwanie zmienić się na użytkownik."
Codeversed
10
Jeśli spojrzę na źródło v4, popBackStackImmediateto natychmiast kończy się niepowodzeniem, jeśli stan został zapisany. Wcześniej dodanie fragmentu z commitAllowingStateLossnie odgrywa żadnej roli. Moje testy pokazują, że to prawda. Nie ma to wpływu na ten konkretny wyjątek. Potrzebujemy popBackStackImmediateAllowingStateLossmetody.
Synesso,
3
@DanieleB tak, opublikowałem odpowiedź tutaj. Ale faktycznie znalazłem jeszcze lepsze rozwiązanie, używając magistrali komunikatów Otto: zarejestruj fragment jako subskrybent i nasłuchuj wyniku asynchronicznego z magistrali. Wyrejestruj się po wstrzymaniu i ponownie zarejestruj się po wznowieniu. Async potrzebuje również metody Produce na czas, gdy zakończy się, a fragment zostanie wstrzymany. Kiedy będę miał czas, zaktualizuję moją odpowiedź bardziej szczegółowo.
Synesso,
130

Istnieje wiele powiązanych problemów z podobnym komunikatem o błędzie. Sprawdź drugą linię tego konkretnego śladu stosu. Ten wyjątek dotyczy w szczególności połączenia z FragmentManagerImpl.popBackStackImmediate.

To wywołanie metody, na przykład popBackStack, zawsze zakończy się niepowodzeniem, IllegalStateExceptionjeśli stan sesji został już zapisany. Sprawdź źródło. Nic nie możesz zrobić, aby zapobiec zgłoszeniu wyjątku.

  • Usunięcie połączenia z super.onSaveInstanceStatenie pomoże.
  • Utworzenie Fragmentu commitAllowingStateLossnie pomoże.

Oto jak zaobserwowałem problem:

  • Jest formularz z przyciskiem Prześlij.
  • Po kliknięciu przycisku tworzone jest okno dialogowe i rozpoczyna się proces asynchroniczny.
  • Użytkownik klika klucz domowy przed zakończeniem procesu - onSaveInstanceStatejest wywoływany.
  • Proces zostaje zakończony, następuje wywołanie zwrotne i popBackStackImmediatenastępuje próba.
  • IllegalStateException Jest rzucony.

Oto, co zrobiłem, aby to rozwiązać:

Ponieważ nie można uniknąć IllegalStateExceptionwywołania zwrotnego, należy go złapać i zignorować.

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

To wystarczy, aby zatrzymać awarię aplikacji. Ale teraz użytkownik przywróci aplikację i zobaczy, że przycisk, który, jak mu się wydawało, nie został w ogóle naciśnięty (ich zdaniem). Fragment formularza wciąż się wyświetla!

Aby to naprawić, po utworzeniu okna dialogowego ustaw stan wskazujący, że proces się rozpoczął.

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

I zapisz ten stan w pakiecie.

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

Nie zapomnij załadować go ponownie onViewCreated

Następnie, po wznowieniu, przywróć fragmenty, jeśli wcześniej podjęto próbę przesłania. Zapobiega to powrotowi użytkownika do czegoś, co wydaje się nieprzesłanym formularzem.

@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
źródło
5
Ciekawa lektura na ten temat tutaj: androiddesignpatterns.com/2013/08/…
Pascal
Jeśli używasz DialogFragment, stworzyłem alternatywę dla niego tutaj: github.com/AndroidDeveloperLB/DialogShard
programista Androida
Co jeśli popBackStackImmediatezostałby wywołany przez sam system Android?
Kimi Chiu,
1
Absolutnie świetne. To powinna być zaakceptowana odpowiedź. Dziękuję Ci bardzo! Może chciałbym dodać submPressed = false; po popBackStackInmediate.
Neonigma
Nie użyłem metody public void onSaveInstanceState (Bundle outState). Czy muszę ustawić pustą metodę publicznej nieważności onSaveInstanceState (Bundle outState)?
Faxriddin Abdullayev
55

Sprawdź aktywność isFinishing()przed pokazaniem fragmentu i zwróć uwagę commitAllowingStateLoss().

Przykład:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}
Naskov
źródło
1
! isFinishing () &&! isDestroyed () nie działa dla mnie.
Allen Vork,
! isFinishing () &&! isDestroyed () działało dla mnie, ale wymaga API 17. Ale po prostu nie pokazuje DialogFragment. Zobacz stackoverflow.com/questions/15729138/... innych rozwiązań dobrych, stackoverflow.com/a/41813953/2914140 pomógł mi.
CoolMind
29

Jest październik 2017 r., A Google tworzy Bibliotekę pomocy technicznej dla systemu Android, dodając nową funkcję Lifecycle. Daje to nowy pomysł na problem „Nie można wykonać tej akcji po wystąpieniu problemu onSaveInstanceState”.

W skrócie:

  • Użyj komponentu cyklu życia, aby ustalić, czy jest to właściwy czas na wyskakujący fragment.

Dłuższa wersja z wyjaśnieniem:

  • dlaczego ten problem się pojawia?

    To dlatego, że próbujesz wykorzystać FragmentManagerswoją aktywność (która, jak sądzę, będzie trzymać twój fragment?), Aby dokonać transakcji dla ciebie. Zwykle wygląda to tak, jakbyś próbował wykonać transakcję dla nadchodzącego fragmentu, tymczasem aktywność hosta już wywołuje savedInstanceStatemetodę (użytkownik może dotknąć przycisku strony głównej, więc aktywność wywołuje onStop(), w moim przypadku jest to powód)

    Zwykle ten problem nie powinien się zdarzyć - zawsze staramy się załadować fragment do działania na samym początku, tak jak onCreate()metoda jest do tego idealnym miejscem. Ale czasami tak się dzieje , szczególnie gdy nie możesz zdecydować, który fragment załadujesz do tej aktywności lub próbujesz załadować fragment z AsyncTaskbloku (lub coś zajmie trochę czasu). Czas, zanim transakcja fragmentu naprawdę się wydarzy, ale po onCreate()metodzie działania użytkownik może zrobić wszystko. Jeśli użytkownik naciśnie przycisk Home, który wyzwala onSavedInstanceState()metodę działania, nastąpi can not perform this actionawaria.

    Jeśli ktoś chce przyjrzeć się temu zagadnieniu głębiej, sugeruję, aby zapoznał się z tym postem na blogu . Wygląda głęboko w warstwie kodu źródłowego i wiele o tym wyjaśnia. Podaje również powód, dla którego nie powinieneś używać tej commitAllowingStateLoss()metody do obejścia tej awarii (zaufaj mi, że nie oferuje nic dobrego dla twojego kodu)

  • Jak to naprawić?

    • Czy powinienem użyć commitAllowingStateLoss()metody do załadowania fragmentu? Nie, nie powinieneś ;

    • Czy powinienem zastąpić onSaveInstanceStatemetodę, zignorować supermetodę w niej zawartą? Nie, nie powinieneś ;

    • Czy powinienem użyć magicznej isFinishingaktywności wewnętrznej, aby sprawdzić, czy aktywność hosta jest w odpowiednim momencie na fragmentację transakcji? Tak, to wygląda na właściwy sposób.

  • Zobacz, co może zrobić komponent Lifecycle .

    Zasadniczo Google wprowadza pewną implementację wewnątrz AppCompatActivityklasy (i kilka innych klas bazowych, których powinieneś użyć w swoim projekcie), co ułatwia określenie bieżącego stanu cyklu życia . Wróć do naszego problemu: dlaczego miałby się tak stać? To dlatego, że robimy coś w niewłaściwym czasie. Staramy się tego nie robić, a ten problem zniknie.

    Trochę koduję dla własnego projektu, oto co robię LifeCycle. Koduję w Kotlinie.

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

Jak pokazałem powyżej. Sprawdzę stan cyklu życia hosta. W przypadku komponentu Lifecycle w ramach biblioteki wsparcia może to być bardziej szczegółowe. Kod lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)oznacza, że ​​jeśli obecny stan jest przynajmniej onResume, nie później niż? Co gwarantuje, że moja metoda nie zostanie wykonana w innym stanie życia (jak onStop).

  • Czy to wszystko zrobione?

    Oczywiście nie. Kod, który pokazałem, pokazuje nowy sposób zapobiegania awarii aplikacji. Ale jeśli przejdzie do stanu onStop, ten wiersz kodu nie zadziała, a tym samym nie pokaże nic na ekranie. Gdy użytkownicy wrócą do aplikacji, zobaczą pusty ekran, czyli puste działanie hosta, w którym nie ma żadnych fragmentów. To złe doświadczenie (tak, trochę lepsze niż awaria).

    Więc tutaj chciałbym, aby mogło być coś ładniejszego: aplikacja nie zawiedzie się, jeśli dojdzie do stanu życia później, niż onResumemetoda transakcji jest świadoma stanu życia; poza tym aktywność będzie próbowała kontynuować tę transakcję fragmentaryczną po powrocie użytkownika do naszej aplikacji.

    Dodaję coś więcej do tej metody:

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

Prowadzę listę w tej dispatcherklasie, aby przechowywać te fragmenty, nie mam szans na zakończenie akcji transakcyjnej. A kiedy użytkownik wróci z ekranu głównego i stwierdzi, że nadal jest fragment, który ma zostać uruchomiony, przejdzie do resume()metody pod @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)adnotacją. Teraz myślę, że powinno działać tak, jak się spodziewałem.

Anthonyeef
źródło
8
Byłoby miło używać Javy zamiast Kotlina
Shchvova
1
Dlaczego twoja implementacja FragmentDispatcherużywa listy do przechowywania oczekujących fragmentów, jeśli przywrócony zostanie tylko jeden fragment?
fraherm
21

Oto inne rozwiązanie tego problemu.

Za pomocą prywatnej zmiennej członkowskiej możesz ustawić zwrócone dane jako zamiar, który można następnie przetworzyć po super.onResume ();

Tak jak:

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;
    }
}
Jed
źródło
7
W zależności od tego, co robiłeś, co było niedozwolone, może to wymagać przejścia do nawet późniejszego momentu niż onResume (). W przypadku insatcne, jeśli problem stanowi FragmentTransaction.commit (), należy zamiast tego przejść do onPostResume ().
pjv,
1
To jest dla mnie odpowiedź na to pytanie. Ponieważ musiałem przekazać otrzymany tag NFC do poprzedniej aktywności, właśnie to zrobiłem dla mnie.
Janis Peisenieks
7
Dla mnie tak się działo, ponieważ nie dzwoniłem super.onActivityResult().
Sufian,
20

Krótkie i działające rozwiązanie:

Wykonaj proste kroki

Kroki

Krok 1: Zastąp onSaveInstanceStatestan w odpowiednim fragmencie. I usuń z niego super metodę.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Krok 2: Użyj fragmentTransaction.commitAllowingStateLoss( );

zamiast fragmentTransaction.commit( ); operacji fragmentowania while.

Vinayak
źródło
Odpowiedź nie jest kopiowana ani odsyłana z innego miejsca, w którym. Zostało wysłane, aby pomóc ludziom przez moje działające rozwiązanie, które otrzymałem przez kilka prób i błędów
Vinayak
12

UWAGA , używanie transaction.commitAllowingStateLoss()może spowodować złe wrażenia dla użytkownika. Aby uzyskać więcej informacji o tym, dlaczego ten wyjątek jest zgłaszany, zobacz ten post .

Eric Brandwein
źródło
6
To nie zapewnia odpowiedzi na pytanie, musisz podać poprawną odpowiedź na pytanie
Umar Ata
10

Znalazłem brudne rozwiązanie dla tego rodzaju problemu. Jeśli nadal chcesz zachować swój ActivityGroupsz jakiegokolwiek powodu (miałem ograniczenia czasowe), po prostu zastosuj

public void onBackPressed() {}

w swojej Activityi zrobić jakiś backkod tam. nawet jeśli nie ma takiej metody na starszych urządzeniach, metoda ta jest wywoływana przez nowsze.

Saberrider
źródło
6

Nie należy używać commitAllowingStateLoss (), należy go używać tylko w przypadkach, w których stan interfejsu użytkownika może się nieoczekiwanie zmienić na użytkownika.

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

Jeśli transakcja ma miejsce w ChildFragmentManager z parentFragment, użyj ParentFragment.isResume () na zewnątrz, aby to sprawdzić.

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
Kupiec
źródło
5

Miałem podobny problem, scenariusz był taki:

  • Moja aktywność polega na dodawaniu / zastępowaniu fragmentów listy.
  • Każdy fragment listy ma odniesienie do działania, aby powiadomić działanie po kliknięciu elementu listy (wzorzec obserwatora).
  • Każdy fragment listy wywołuje setRetainInstance (true); w metodzie onCreate .

OnCreate Sposób działania był jak poniżej:

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

Zgłoszono wyjątek, ponieważ gdy zmienia się konfiguracja (urządzenie zostało obrócone), aktywność jest tworzona, główny fragment jest pobierany z historii menedżera fragmentów, a jednocześnie fragment ma już OLD odwołanie do zniszczonej aktywności

zmiana implementacji na to rozwiązała problem:

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

musisz ustawić słuchaczy za każdym razem, gdy działanie jest tworzone, aby uniknąć sytuacji, w której fragmenty zawierają odniesienia do starych zniszczonych instancji działania.

Mina Samy
źródło
5

Jeśli odziedziczysz po FragmentActivity, musisz wywołać nadklasę w onActivityResult():

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

Jeśli tego nie zrobisz i spróbujesz wyświetlić okno dialogowe fragmentu w tej metodzie, możesz otrzymać OP IllegalStateException. (Szczerze mówiąc, nie do końca rozumiem, dlaczego super połączenie rozwiązuje problem. onActivityResult()Jest wywoływany wcześniej onResume(), więc nadal nie powinno być dozwolone wyświetlanie okna dialogowego fragmentu).

Lawrence Kesteloot
źródło
1
Chciałbym wiedzieć, dlaczego to rozwiązuje problem.
Big McLargeHuge
3

Otrzymywałem ten wyjątek, kiedy naciskałem przycisk Wstecz, aby anulować zamiar wyboru mojej aktywności fragmentu mapy. Rozwiązałem ten problem, zastępując kod onResume (gdzie inicjowałem fragment) na onstart () i aplikacja działa dobrze. Mam nadzieję, że to pomaga.

DCS
źródło
2

Myślę, że używanie transaction.commitAllowingStateLoss();nie jest najlepszym rozwiązaniem. Ten wyjątek zostanie zgłoszony, gdy zmieni się konfiguracja działania i onSavedInstanceState()zostanie wywołany fragment, a następnie metoda wywołania zwrotnego asynchronicznego spróbuje zatwierdzić fragment.

Prostym rozwiązaniem może być sprawdzenie, czy aktywność zmienia konfigurację, czy nie

np. czek isChangingConfigurations()

to znaczy

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

Sprawdź również ten link

Amol Desai
źródło
Jakoś dostałem ten wyjątek, gdy użytkownik coś kliknie (kliknięcie jest wyzwalaczem do wykonania transakcji-zatwierdzenia). Jak to może być? Czy tutaj byłoby twoje rozwiązanie?
programista Androida
@androiddeveloper, co jeszcze robisz po kliknięciu użytkownika. fragment jakoś zapisuje swój stan przed dokonaniem transakcji
Amol Desai
Wyjątek został zgłoszony na dokładną linię zatwierdzenia transakcji. Miałem też dziwną literówkę: zamiast „tutaj tutaj” miałem na myśli „pracę tutaj”.
programista Androida
@androiddeveloper masz rację! ale przed dokonaniem transakcji pojawia się jakiś wątek w tle lub coś takiego?
Amol Desai
Nie wydaje mi się (przepraszam, że jestem poza biurem), ale dlaczego miałoby to mieć znaczenie? Tu są wszystkie elementy interfejsu użytkownika ... Gdybym kiedykolwiek zrobił coś na tle wątku, miałbym tam wyjątki, a ponadto nie umieszczam elementów związanych z interfejsem użytkownika na wątkach w tle, ponieważ jest to zbyt ryzykowne.
programista Androida
2

Być może najprostszym i najprostszym rozwiązaniem, jakie znalazłem w moim przypadku, było uniknięcie wyskakiwania uciążliwego fragmentu ze stosu w odpowiedzi na wynik działania. Więc zmieniając to połączenie w moim onActivityResult():

popMyFragmentAndMoveOn();

do tego:

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

pomógł w moim przypadku.

mojuba
źródło
2

Jeśli wykonujesz jakąś FragmentTransaction w onActivityResult, co możesz zrobić, możesz ustawić pewną wartość logiczną wewnątrz onActivityResult, a następnie w onResume możesz wykonać FragmentTransaction na podstawie wartości boolowskiej. Proszę odnieść się do kodu poniżej.

@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
źródło
Nie wysyłaj kodu jako obrazu, zamiast tego użyj formatowania kodu.
Micer
1
Tak, pomogło mi to. To dokładne i właściwe rozwiązanie dla urządzenia Zefir dostałem ten wyjątek i rozwiązałem tą prostą sztuczką .. !! Stąd głosowałem.
sandhya sasane
2

Dzięki uprzejmości: Rozwiązanie dla IllegalStateException

Ta kwestia denerwowała mnie przez długi czas, ale na szczęście znalazłem konkretne rozwiązanie. Szczegółowe wyjaśnienie znajduje się tutaj .

Użycie commitAllowStateloss () może zapobiec temu wyjątkowi, ale doprowadziłoby do nieprawidłowości interfejsu użytkownika. Do tej pory zrozumieliśmy, że występuje błąd IllegalStateException, gdy próbujemy zatwierdzić fragment po utracie stanu działania - dlatego powinniśmy po prostu opóźnić transakcję, dopóki stan nie zostanie przywrócony Można to po prostu zrobić w ten sposób

Zadeklaruj dwie prywatne zmienne boolowskie

 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;

Teraz w onPostResume () i onPause ustawiamy i wyłączamy naszą zmienną logiczną isTransactionSafe. Ideą jest, aby oznaczyć transakcję jako bezpieczną tylko wtedy, gdy aktywność jest na pierwszym planie, aby nie było szansy na statelossa.

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

-Co zrobiliśmy do tej pory zaoszczędzi od IllegalStateException, ale nasze transakcje zostaną utracone, jeśli zostaną wykonane po przeniesieniu aktywności do tła, podobnie jak commitAllowStateloss (). Aby temu zaradzić, mamy zmienną logiczną 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
źródło
2

Transakcje fragmentów nie powinny być później wykonywane Activity.onStop()! Sprawdź, czy nie masz żadnych wywołań zwrotnych, które mogłyby wykonać transakcję po onStop(). Lepiej jest ustalić przyczynę zamiast próbować obejść problem za pomocą takich podejść.commitAllowingStateLoss()

Ivo Stoyanov
źródło
1

Począwszy od biblioteki obsługi wersji 24.0.0 można wywołać FragmentTransaction.commitNow()metodę, która zatwierdza transakcję synchronicznie zamiast wywoływania, commit()a następnie executePendingTransactions(). Jak wynika z dokumentacji , takie podejście jest jeszcze lepsze:

Wywołanie commitNow jest lepsze niż wywołanie commit (), po którym następuje executePendingTransactions (), ponieważ ten ostatni będzie miał efekt uboczny próby zatwierdzenia wszystkich aktualnie oczekujących transakcji, niezależnie od tego, czy jest to pożądane zachowanie, czy nie.

Włodzimierz
źródło
1

Ilekroć próbujesz załadować fragment do swojej aktywności, upewnij się, że aktywność jest wznawiana i nie będzie wstrzymywać stanu. W stanie wstrzymania może się skończyć utratą wykonanej operacji zatwierdzania.

Do ładowania fragmentu można użyć transakcji.commitAllowingStateLoss () zamiast transakcji.commit ()

lub

Utwórz wartość logiczną i sprawdź, czy aktywność nie zostanie wstrzymana

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

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

następnie podczas ładowania sprawdzanie fragmentów

if(mIsResumed){
//load the your fragment
}
Sharath Kumar
źródło
1

Aby ominąć ten problem, możemy użyć Komponentu architektury nawigacji , który został wprowadzony w Google I / O 2018. Komponentem architektury nawigacji upraszcza implementację nawigacji w aplikacji na Androida.

Levon Petrosyan
źródło
To nie jest wystarczająco dobre i błędne (słaba obsługa linków głębokich, nie można zapisać stanu pokaż / ukryj fragmenty i niektóre ważne kwestie, które są nadal otwarte)
do01
1

Jeśli chodzi o świetną odpowiedź na @Anthonyeef, oto przykładowy kod w Javie:

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
źródło
1

Jeśli masz awarię metody popBackStack () lub popBackStackImmediate (), spróbuj naprawić to:

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

To również zadziałało dla mnie.

Tapa Save
źródło
Zauważ, że wymaga API 26 i wyższych
Itay Feldman
1

W moim przypadku ten błąd wystąpił w metodzie zastępowania o nazwie onActivityResult. Po kopaniu po prostu stwierdziłem, że może wcześniej musiałem nazwać „ super ”.
Dodałem to i po prostu zadziałało

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

}

Może po prostu musisz dodać „super” do każdego zastąpienia, które robisz przed kodem.

Gian Gomen
źródło
1

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

Stosowanie:

val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)
CoolMind
źródło
Możesz usunąć () -> przed Fragmentem
Claire
@ Claire, dzięki! Masz na myśli przejście na fragment: Fragment? Tak, wypróbowałem ten wariant, ale w tym przypadku fragment zostałby utworzony we wszystkich przypadkach (nawet gdy fragmentManager == null, ale nie spotkałem się z tą sytuacją). Zaktualizowałem odpowiedź i zmieniłem null na tag addToBackStack().
CoolMind
1

Ta awaria jest spowodowana popełnieniem transakcji FragmentTransaction po tym, jak cykl życia jej działania uruchomił się już naSaveInstanceState. Jest to często spowodowane zatwierdzeniem FragmentTransactions z asynchronicznego wywołania zwrotnego. Sprawdź połączony zasób, aby uzyskać więcej informacji.

Transakcje fragmentów i utrata stanu aktywności

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

Bholendra Singh
źródło
0

Dodaj to do swojej aktywności

@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);
    }
}
Yifan
źródło
0

Wystąpił również ten problem i problem pojawia się za każdym razem, gdy FragmentActivityzmienia się kontekst twojego kontekstu (np. Zmienia się orientacja ekranu itp.). Tak więc najlepszym rozwiązaniem jest aktualizacja kontekstu z twojego FragmentActivity.

Adam
źródło
0

Skończyłem z utworzeniem fragmentu podstawowego i sprawiłem, aby wszystkie fragmenty w mojej aplikacji go rozszerzyły

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

Potem, gdy próbuję pokazać fragment, którego używam showAllowingStateLoss zamiastshow

lubię to:

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

Wymyśliłem to rozwiązanie z tego PR: https://github.com/googlesamples/easypermissions/pull/170/files

Ahmad El-Melegy
źródło
0

Kolejne możliwe obejście, którego nie jestem pewien, czy pomaga we wszystkich przypadkach (pochodzenie tutaj ):

@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();
        }
    }
}
programista Androida
źródło
0

Wiem, że @Ovidiu Latcu zaakceptowała odpowiedź, ale po pewnym czasie błąd nadal występuje.

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

Crashlytics wciąż wysyła mi ten dziwny komunikat o błędzie.

Jednak błąd występuje teraz tylko w wersji 7+ (Nougat). Moją poprawką było użycie commitAllowingStateLoss () zamiast commit () w fragmentTransaction.

Ten post jest pomocny dla commitAllowingStateLoss () i nigdy więcej nie miał problemu z fragmentem.

Podsumowując, przyjęta tutaj odpowiedź może działać na wersjach Androida starszych niż Nougat.

Może to zaoszczędzić komuś kilka godzin wyszukiwania. szczęśliwe kodowanie. <3 okrzyki

ralphgabb
źródło