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.
android
android-fragments
android-viewpager
illegalstateexception
fragmenttransaction
nhaarman
źródło
źródło
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?Odpowiedzi:
Sprawdź moją odpowiedź tutaj . Zasadniczo musiałem po prostu:
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:Ostatecznie właściwym rozwiązaniem było (jak widać w komentarzach) użycie:
podczas dodawania lub wykonywania tego,
FragmentTransaction
który był przyczynąException
.źródło
popBackStackImmediate
to natychmiast kończy się niepowodzeniem, jeśli stan został zapisany. Wcześniej dodanie fragmentu zcommitAllowingStateLoss
nie odgrywa żadnej roli. Moje testy pokazują, że to prawda. Nie ma to wpływu na ten konkretny wyjątek. PotrzebujemypopBackStackImmediateAllowingStateLoss
metody.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,IllegalStateException
jeśli stan sesji został już zapisany. Sprawdź źródło. Nic nie możesz zrobić, aby zapobiec zgłoszeniu wyjątku.super.onSaveInstanceState
nie pomoże.commitAllowingStateLoss
nie pomoże.Oto jak zaobserwowałem problem:
onSaveInstanceState
jest wywoływany.popBackStackImmediate
następuje próba.IllegalStateException
Jest rzucony.Oto, co zrobiłem, aby to rozwiązać:
Ponieważ nie można uniknąć
IllegalStateException
wywołania zwrotnego, należy go złapać i zignorować.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ął.
I zapisz ten stan w pakiecie.
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.
źródło
popBackStackImmediate
zostałby wywołany przez sam system Android?Sprawdź aktywność
isFinishing()
przed pokazaniem fragmentu i zwróć uwagęcommitAllowingStateLoss()
.Przykład:
źródło
DialogFragment
. Zobacz stackoverflow.com/questions/15729138/... innych rozwiązań dobrych, stackoverflow.com/a/41813953/2914140 pomógł mi.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:
Dłuższa wersja z wyjaśnieniem:
dlaczego ten problem się pojawia?
To dlatego, że próbujesz wykorzystać
FragmentManager
swoją 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łujesavedInstanceState
metodę (użytkownik może dotknąć przycisku strony głównej, więc aktywność wywołujeonStop()
, 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 zAsyncTask
bloku (lub coś zajmie trochę czasu). Czas, zanim transakcja fragmentu naprawdę się wydarzy, ale poonCreate()
metodzie działania użytkownik może zrobić wszystko. Jeśli użytkownik naciśnie przycisk Home, który wyzwalaonSavedInstanceState()
metodę działania, nastąpican not perform this action
awaria.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ć
onSaveInstanceState
metodę, zignorowaćsuper
metodę w niej zawartą? Nie, nie powinieneś ;Czy powinienem użyć magicznej
isFinishing
aktywnoś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
AppCompatActivity
klasy (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.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 przynajmniejonResume
, nie później niż? Co gwarantuje, że moja metoda nie zostanie wykonana w innym stanie życia (jakonStop
).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ż
onResume
metoda 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:
Prowadzę listę w tej
dispatcher
klasie, 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 doresume()
metody pod@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
adnotacją. Teraz myślę, że powinno działać tak, jak się spodziewałem.źródło
FragmentDispatcher
używa listy do przechowywania oczekujących fragmentów, jeśli przywrócony zostanie tylko jeden fragment?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:
źródło
super.onActivityResult()
.Krótkie i działające rozwiązanie:
Wykonaj proste kroki
Kroki
Krok 1: Zastąp
onSaveInstanceState
stan w odpowiednim fragmencie. I usuń z niego super metodę.Krok 2: Użyj
fragmentTransaction.commitAllowingStateLoss( );
zamiast
fragmentTransaction.commit( );
operacji fragmentowania while.źródło
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 .źródło
Znalazłem brudne rozwiązanie dla tego rodzaju problemu. Jeśli nadal chcesz zachować swój
ActivityGroups
z jakiegokolwiek powodu (miałem ograniczenia czasowe), po prostu zastosujw swojej
Activity
i zrobić jakiśback
kod tam. nawet jeśli nie ma takiej metody na starszych urządzeniach, metoda ta jest wywoływana przez nowsze.źródło
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ć.
źródło
Miałem podobny problem, scenariusz był taki:
OnCreate Sposób działania był jak poniżej:
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:
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.
źródło
Jeśli odziedziczysz po
FragmentActivity
, musisz wywołać nadklasę wonActivityResult()
: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śniejonResume()
, więc nadal nie powinno być dozwolone wyświetlanie okna dialogowego fragmentu).źródło
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.
źródło
Myślę, że używanie
transaction.commitAllowingStateLoss();
nie jest najlepszym rozwiązaniem. Ten wyjątek zostanie zgłoszony, gdy zmieni się konfiguracja działania ionSavedInstanceState()
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
źródło
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()
:do tego:
pomógł w moim przypadku.
źródło
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.
źródło
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
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.
-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
źródło
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ę poonStop()
. Lepiej jest ustalić przyczynę zamiast próbować obejść problem za pomocą takich podejść.commitAllowingStateLoss()
źródło
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ępnieexecutePendingTransactions()
. Jak wynika z dokumentacji , takie podejście jest jeszcze lepsze:źródło
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
następnie podczas ładowania sprawdzanie fragmentów
źródło
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.
źródło
Jeśli chodzi o świetną odpowiedź na @Anthonyeef, oto przykładowy kod w Javie:
źródło
Jeśli masz awarię metody popBackStack () lub popBackStackImmediate (), spróbuj naprawić to:
To również zadziałało dla mnie.
źródło
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
Może po prostu musisz dodać „super” do każdego zastąpienia, które robisz przed kodem.
źródło
Rozszerzenie Kotlin
Stosowanie:
źródło
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 tagaddToBackStack()
.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
źródło
Dodaj to do swojej aktywności
źródło
Wystąpił również ten problem i problem pojawia się za każdym razem, gdy
FragmentActivity
zmienia się kontekst twojego kontekstu (np. Zmienia się orientacja ekranu itp.). Tak więc najlepszym rozwiązaniem jest aktualizacja kontekstu z twojegoFragmentActivity
.źródło
Skończyłem z utworzeniem fragmentu podstawowego i sprawiłem, aby wszystkie fragmenty w mojej aplikacji go rozszerzyły
Potem, gdy próbuję pokazać fragment, którego używam
showAllowingStateLoss
zamiastshow
lubię to:
Wymyśliłem to rozwiązanie z tego PR: https://github.com/googlesamples/easypermissions/pull/170/files
źródło
Kolejne możliwe obejście, którego nie jestem pewien, czy pomaga we wszystkich przypadkach (pochodzenie tutaj ):
źródło
Wiem, że @Ovidiu Latcu zaakceptowała odpowiedź, ale po pewnym czasie błąd nadal występuje.
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
źródło