uzyskiwanie wyjątku „IllegalStateException: Nie można wykonać tej akcji po onSaveInstanceState”

355

Mam aplikację na Androida na żywo, a z rynku otrzymałem śledzenie stosu i nie mam pojęcia, dlaczego dzieje się tak, ponieważ nie dzieje się w kodzie aplikacji, ale jest spowodowane przez jakieś lub inne zdarzenie z aplikacji (założenie)

Nie używam Fragments, wciąż istnieje odwołanie do FragmentManager. Jeśli jakikolwiek organ może rzucić nieco światła na niektóre ukryte fakty, aby uniknąć tego rodzaju problemów:

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.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
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.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:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
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:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)  
chłodzenie
źródło
Znalazłeś już rozwiązanie? Mając ten sam problem tutaj: stackoverflow.com/questions/7575921/…
nhaarman
2
@phlebas Nie, nie zrobiłeś tego. Twoje dotyczy okien dialogowych, a to nie dotyczy. Górna linia dopasowania śledzenia stosu nie jest wystarczająca. Reszta jest zupełnie inna. Mówię to, bo właśnie spojrzałem na twój problem i niestety to mi nie pomaga.
themightyjon
Czy używasz Thread lub AsynTask w tym działaniu?
José Castro,
21
Ten błąd omawiam w moim poście na blogu ... powinieneś go przeczytać. :)
Alex Lockwood,

Odpowiedzi:

455

To najgłupszy błąd, jaki do tej pory napotkałem. Miałem Fragmentaplikacji doskonale pracuje dla API <11 , a Force Closingna API> 11 .

Naprawdę nie mogłem dowiedzieć się, co zmienili w Activitycyklu życia w wezwaniu do saveInstance, ale oto jak to rozwiązałem:

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

Po prostu nie dzwonię .super()i wszystko działa świetnie. Mam nadzieję, że pozwoli ci to zaoszczędzić trochę czasu.

EDYCJA: po kilku dalszych badaniach 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);
}

EDYCJA 2: może się to również zdarzyć, jeśli próbujesz wykonać transakcję po tym, jak nie Activityma Cię w tle. Aby tego uniknąć, powinieneś użyćcommitAllowingStateLoss()

EDYCJA 3: Powyższe rozwiązania naprawiały problemy we wczesnych bibliotekach support.v4 z tego, co pamiętam. Ale jeśli nadal masz z tym problemy, MUSISZ także przeczytać blog @AlexLockwood : Fragment Transactions & Activity State Loss

Podsumowanie z postu na blogu (ale zdecydowanie polecam go przeczytać):

  • NIGDY nie commit() przeprowadzaj transakcji po onPause()Honeycomb i onStop()po Honeycomb
  • Zachowaj ostrożność przy dokonywaniu transakcji w ramach Activitymetod cyklu życia. Zastosowanie onCreate() , onResumeFragments()aonPostResume()
  • Unikaj wykonywania transakcji w ramach asynchronicznych metod wywołania zwrotnego
  • Używaj commitAllowingStateLoss()tylko w ostateczności
Ovidiu Latcu
źródło
97
Powinieneś użyć commitAllowingStateLoss () zamiast commit ()
Meh
7
Nie wywołanie super w onSaveInstanceState spowoduje, że FragmentManager nie będzie w stanie zapisać stanu wszystkich fragmentów i je przywrócić. Może to powodować problemy z rotacją. Wypróbowałem też inną rzecz dotyczącą umieszczania śmieci w pakiecie i to nie ma dla mnie znaczenia. Nie jestem pewien, jak by to było - błąd, o którym wspomniałeś w pakiecie wsparcia, to NullPointerException, i nie wydaje się podobny do tego IllegalStateException ...
themightyjon
56
@meh commitAllowingStateLoss()unika tylko wyjątku. Nie chroni aplikacji przed przypadkową utratą stanu. Zobacz ten post na blogu .
Alex Lockwood,
2
@AlexLockwood, więc z tego postu na blogu możemy dowiedzieć się, że powinniśmy wykonywać wszystkie nasze połączenia sieciowe wewnątrz fragmentu (i wyświetlać tymczasowy interfejs użytkownika w razie potrzeby) i to jest jedyny sposób, aby uniknąć tego wyjątku, gdy prawdopodobnie wystąpi, ponieważ zatwierdzenie jest wywoływany po asynchronicznym wywołaniu metody.
meh,
1
Nie dostaję pierwszego punktu: „NIGDY nie zatwierdzaj () transakcji po ... onStop () na post-Honeycomb”. Co zrobić, jeśli potrzebuję przycisku, aby uruchomić fragment do zastąpienia innym? Czy powinienem wstawić wartość logiczną, która sprawdza, czy działanie zakończyło się podczas zatrzymania, a jeśli tak, to zamiast tego wywołać commitAllowingStateLoss? A co jeśli mam fragment w obrębie fragmentu, który muszę wymienić po kliknięciu przycisku?
programista Androida
76

Po zapoznaniu się z kodem źródłowym Androida, co powoduje ten problem, flaga mStateSaved w FragmentManagerImplklasie (instancja dostępna w działaniu) ma wartość true. Jest ustawiony na true, gdy tylny stos jest zapisywany (saveAllState) podczas połączenia z Activity#onSaveInstanceState. Następnie połączenia z ActivityThread nie resetują tej flagi przy użyciu dostępnych metod resetowania z FragmentManagerImpl#noteStateNotSaved()i dispatch().

Widzę, że jest kilka dostępnych poprawek, w zależności od tego, co robi Twoja aplikacja i używa:

Dobre sposoby

Przede wszystkim: reklamowałbym artykuł Alexa Lockwooda . Następnie z tego, co zrobiłem do tej pory:

  1. W przypadku fragmentów i działań, które nie muszą przechowywać żadnych informacji o stanie, wywołaj commitAllowStateLoss . Zaczerpnięte z dokumentacji:

    Umożliwia wykonanie zatwierdzenia po zapisaniu stanu działania. Jest to niebezpieczne, ponieważ zatwierdzenie może zostać utracone, jeśli działanie będzie musiało zostać później przywrócone ze stanu, więc powinno się go używać tylko w przypadkach, gdy stan interfejsu użytkownika jest nieoczekiwany dla użytkownika`. Sądzę, że można to wykorzystać, jeśli fragment pokazuje informacje tylko do odczytu. Lub nawet jeśli pokazują edytowalne informacje, użyj metod wywołań zwrotnych, aby zachować edytowane informacje.

  2. Zaraz po zatwierdzeniu transakcji (właśnie dzwoniłeś commit()), zadzwoń do FragmentManager.executePendingTransactions().

Niezalecane sposoby:

  1. Jak wspomniano powyżej Ovidiu Latcu, nie dzwoń super.onSaveInstanceState(). Ale to oznacza, że ​​stracisz cały stan swojej aktywności wraz ze stanem fragmentów.

  2. Zastąp, onBackPresseda tam tylko połączenie finish(). To powinno być OK, jeśli twoja aplikacja nie używa Fragments API; jak super.onBackPressedtam jest wezwanie do FragmentManager#popBackStackImmediate().

  3. Jeśli używasz interfejsu API Fragments, a stan Twojej aktywności jest ważny / ważny, możesz spróbować zadzwonić za pomocą interfejsu API refleksji FragmentManagerImpl#noteStateNotSaved(). Ale to jest włamanie, albo można powiedzieć, że to obejście. Nie podoba mi się to, ale w moim przypadku jest to całkiem do przyjęcia, ponieważ mam kod ze starej aplikacji, która używa przestarzałego kodu ( TabActivityi niejawnie LocalActivityManager).

Poniżej znajduje się kod wykorzystujący odbicie:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    invokeFragmentManagerNoteStateNotSaved();
}

@SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeFragmentManagerNoteStateNotSaved() {
    /**
     * For post-Honeycomb devices
     */
    if (Build.VERSION.SDK_INT < 11) {
        return;
    }
    try {
        Class cls = getClass();
        do {
            cls = cls.getSuperclass();
        } while (!"Activity".equals(cls.getSimpleName()));
        Field fragmentMgrField = cls.getDeclaredField("mFragments");
        fragmentMgrField.setAccessible(true);

        Object fragmentMgr = fragmentMgrField.get(this);
        cls = fragmentMgr.getClass();

        Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
        noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
        Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
    } catch (Exception ex) {
        Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
    }
}

Twoje zdrowie!

gunar
źródło
Wydaje się, że dzieje się tak również w przypadku ActionBarSherlock, pod Gingerbread, więc przypadek sprawdzania Kompilacji Id wydaje się być dyskusyjny ... :(
t0mm13b
Należy również wskazać - to też nie nadaje się do użycia ABS :)
t0mm13b
@ t0mm13b: Powyższy kod oznacza mój projekt, ponieważ nie używa on ani fragmentów, ani obsługi.FragmentActivity. Działa na android.app.Activity, a ponieważ niespójność, która powoduje wyjątek, jest spowodowana przez FragmentManager (poziom API 11 w górę), dlatego sprawdź ... Jeśli uważasz, że źródło zła jest dla ciebie takie samo, nie krępuj się aby usunąć czek. ABS to inna historia, ponieważ działa na pakiecie kompatybilności i implementacji wsparcia. FractmentActivity może korzystać z tej samej implementacji FragmentManager i voila: ten sam problem.
gunar
@ t0mm13b: aby dodać więcej, ponieważ 600 znaków nie wystarczy, musisz najpierw zbadać, co tak naprawdę jest dla ciebie przyczyną. Musisz także zdać sobie sprawę, że powyższy to brzydki hack i nie biorę żadnej odpowiedzialności, jeśli się uruchomi, czy nie (dla mnie było to najlepsze rozwiązanie biorąc pod uwagę okoliczności). Jeśli musisz go użyć, sprawdź dwukrotnie kod źródłowy kompatybilny dla nazw zmiennych, ponieważ mogą one różnić się od standardowego pakietu. Mam nadzieję, że ten problem zostanie rozwiązany w następnych wersjach pakietu kompatybilności, ale z doświadczenia Androida nie ma wielu szans ...
gunar
Uhhh ... To dokładnie ten sam problem, co w tym raporcie o błędzie, który jest sednem pytania tego OP. Podtrzymuję mój komentarz - powinieneś jawnie złożyć oświadczenie i powiedzieć, że nie jest to gwarantowane, a także powinieneś stwierdzić, że nie użyłeś fragmentów - w przeciwnym razie po co zawracać sobie tym głowę odpowiedzią! :) Tylko mówię ...
t0mm13b
35

Taki wyjątek wystąpi, jeśli spróbujesz wykonać przejście fragmentu po onSaveInstanceState()wywołaniu aktywności fragmentu .

Jednym z powodów takiego stanu rzeczy może być pozostawienie uruchomionego AsyncTask(lub Thread) działania, gdy działanie zostanie zatrzymane.

Wywołane później przejścia onSaveInstanceState()mogą potencjalnie zostać utracone, jeśli system odzyska aktywność dla zasobów i odtworzy ją później.

FunkTheMonk
źródło
2
Hej Funk, mam tutaj pytanie, w jaki sposób można wywołać onBackPressed dla tej aktywności lub fragmentu, jeśli ta aktywność lub fragment został zatrzymany. Powyższy wyjątek wydaje się być generowany na podstawie jakiegoś zdarzenia w interfejsie użytkownika (tj. Naciśnięcia klawisza BACK), jednak nie jestem w stanie znaleźć relacji między Zadaniem Asynchronicznym a klawiszem Wstecz.
dcool,
Ponieważ możesz zapisać przejścia fragmentów do stanu wstecznego, naciśnięcie przycisku wstecz może spowodować odwrócenie zapisanego przejścia (tak, że stare fragmenty powrócą). onSaveInstanceState jest wywoływany przed zniszczeniem aktywności w celu przywrócenia zasobów w systemie, nie zawsze po wywołaniu onStop. Przepraszam, to nie było bardzo jasne w mojej odpowiedzi.
FunkTheMonk,
Funk, ale nie używam żadnych fragmentów w mojej aplikacji. Mogą to być fragmenty użyte w kodzie natywnym. wcześniej myślałem, że mówisz o tym samym.
dcool,
1
Miałem AsyncTask z odniesieniem do fragmentu. Problem rozwiązany po usunięciu wywołania super () z onSaveInstanceState i zamianie odwołania z mojej AsyncTask na WeakReference <Fragment>.
Buffalo
2
@Buffalo To zdecydowanie nie jest rozwiązanie problemu. Zawsze powinieneś zadzwonić super.onSaveInstanceState().
Alex Lockwood
27

Wystarczy wywołać super.onPostResume () przed pokazaniem fragmentu lub przenieść kod w metodzie onPostResume () po wywołaniu super.onPostResume (). To rozwiązuje problem!

MJ.Ahmadi
źródło
6
Wywołanie onPostResume () zapewnia, że ​​onResumeFragments () zostanie wywołany i dla mnie jest to idealne rozwiązanie.
j2emanue
20

Może się to również zdarzyć podczas wywoływania dismiss()fragmentu okna dialogowego po zablokowaniu ekranu \ wygaszeniu i zapisaniu stanu instancji Aktywność + okno dialogowe. Aby obejść to połączenie:

dismissAllowingStateLoss()

Dosłownie za każdym razem, gdy zamykam okno dialogowe, nie dbam już o jego stan, więc możesz to zrobić - właściwie nie tracisz żadnego stanu.

Brian Dilley
źródło
2
To był mój dokładny problem! Pan jest genialny!
Tash Pemhiwa
17

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

Wykonaj proste kroki:

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

@Override
public void onSaveInstanceState(Bundle outState) {
};

Krok 2 : Użyj CommitAllowingStateLoss (); zamiast commit (); podczas operacji fragmentacji.

fragmentTransaction.commitAllowingStateLoss();
Basbous
źródło
1
Usunięcie super metody pomogło, czy możesz wyjaśnić dlaczego? Czy można to bezpiecznie usunąć?
Bruce
7
usunięcie super () nie jest bezpieczne, po tym stracisz inne dane conf!
Deadfish
7

to zadziałało dla mnie ... odkryłem to na własną rękę ... mam nadzieję, że ci to pomoże!

1) NIE mają globalnego „statycznego” FragmentManager / FragmentTransaction.

2) onCreate, ZAWSZE inicjuj FragmentManager ponownie!

próbka poniżej: -

public abstract class FragmentController extends AnotherActivity{
protected FragmentManager fragmentManager;
protected FragmentTransaction fragmentTransaction;
protected Bundle mSavedInstanceState;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mSavedInstanceState = savedInstanceState;
    setDefaultFragments();
}

protected void setDefaultFragments() {
    fragmentManager = getSupportFragmentManager();
    //check if on orientation change.. do not re-add fragments!
    if(mSavedInstanceState == null) {
        //instantiate the fragment manager

        fragmentTransaction = fragmentManager.beginTransaction();

        //the navigation fragments
        NavigationFragment navFrag = new NavigationFragment();
        ToolbarFragment toolFrag = new ToolbarFragment();

        fragmentTransaction.add(R.id.NavLayout, navFrag, "NavFrag");
        fragmentTransaction.add(R.id.ToolbarLayout, toolFrag, "ToolFrag");
        fragmentTransaction.commitAllowingStateLoss();

        //add own fragment to the nav (abstract method)
        setOwnFragment();
    }
}
kurayami88
źródło
6

Zawsze otrzymywałem to, gdy próbowałem pokazać fragment w metodzie onActivityForResult (), więc problem był następny:

  1. Moja aktywność jest wstrzymana i zatrzymana, co oznacza, że ​​onSaveInstanceState () został już wywołany (zarówno dla urządzeń o strukturze plastra miodu, jak i po strukturze plastra miodu).
  2. W przypadku jakiegokolwiek wyniku dokonałem transakcji, aby pokazać / ukryć fragment, co powoduje ten wyjątek IllegalStateException.

To, co zrobiłem, jest następne:

  1. Wartość dodana do określania, czy wybrana czynność została wykonana (np. Robienie zdjęcia z kamery - isPhotoTaken) - może być wartością logiczną lub całkowitą, w zależności od tego, ile różnych transakcji potrzebujesz.
  2. W przesłoniętej metodzie onResumeFragments () sprawdziłem swoją wartość i po dokonaniu transakcji fragmentu potrzebowałem. W tym przypadku commit () nie zostało wykonane po onSaveInstanceState, ponieważ stan został zwrócony w metodzie onResumeFragments ().
Szyk
źródło
5

Rozwiązałem problem ze zmianą konfiguracji. Sztuczka polega na tym, że zgodnie z cyklem życia aktywności Androida, gdy wyraźnie nazwałeś zamiar (zamiar kamery lub jakikolwiek inny); działanie zostaje wstrzymane i zostaje zapisane. W takim przypadku wywoływana jest instancja. Podczas obracania urządzenia w inne miejsce niż to, w którym aktywność była aktywna; wykonywanie operacji fragmentów, takich jak zatwierdzanie fragmentów, powoduje wyjątek dotyczący stanu niezgodnego z prawem. Jest na to wiele skarg. Chodzi o zarządzanie cyklem życia aktywności Androida i odpowiednie wywołania metod. Aby to rozwiązać, zrobiłem to: 1-Zastąp metodę onsavedInstance Twojej aktywności i określ bieżącą orientację ekranu (pionową lub poziomą), a następnie ustaw na nią orientację ekranu przed wstrzymaniem aktywności. w ten sposób działanie blokuje obrót ekranu dla działania w przypadku, gdy zostało ono obrócone przez inne. 2, następnie zastąp metodę wznawiania działania i ustaw teraz tryb orientacji na czujnik, aby po wywołaniu metody zachowanej wywoła jeszcze raz konfigurację, aby poprawnie poradzić sobie z obrotem.

Możesz skopiować / wkleić ten kod do swojej aktywności, aby sobie z tym poradzić:

@Override
protected void onSaveInstanceState(Bundle outState) {       
    super.onSaveInstanceState(outState);

    Toast.makeText(this, "Activity OnResume(): Lock Screen Orientation ", Toast.LENGTH_LONG).show();
    int orientation =this.getDisplayOrientation();
    //Lock the screen orientation to the current display orientation : Landscape or Potrait
    this.setRequestedOrientation(orientation);
}

//A method found in stackOverflow, don't remember the author, to determine the right screen orientation independently of the phone or tablet device 
public int getDisplayOrientation() {
    Display getOrient = getWindowManager().getDefaultDisplay();

    int orientation = getOrient.getOrientation();

    // Sometimes you may get undefined orientation Value is 0
    // simple logic solves the problem compare the screen
    // X,Y Co-ordinates and determine the Orientation in such cases
    if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        Configuration config = getResources().getConfiguration();
        orientation = config.orientation;

        if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        // if height and widht of screen are equal then
        // it is square orientation
            if (getOrient.getWidth() == getOrient.getHeight()) {
                orientation = Configuration.ORIENTATION_SQUARE;
            } else { //if widht is less than height than it is portrait
                if (getOrient.getWidth() < getOrient.getHeight()) {
                    orientation = Configuration.ORIENTATION_PORTRAIT;
                } else { // if it is not any of the above it will defineitly be landscape
                    orientation = Configuration.ORIENTATION_LANDSCAPE;
                }
            }
        }
    }
    return orientation; // return value 1 is portrait and 2 is Landscape Mode
}

@Override
public void onResume() {
    super.onResume();
    Toast.makeText(this, "Activity OnResume(): Unlock Screen Orientation ", Toast.LENGTH_LONG).show();
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
} 
douggynix
źródło
4

Miałem ten sam problem, otrzymywanie IllegalStateException, ale zastąpienie wszystkich moich wywołań funkcji commit () funkcją commitAllowingStateLoss () nie pomogło.

Sprawcą było wywołanie DialogFragment.show ().

Otaczam to

try {
    dialog.show(transaction, "blah blah");
}
catch(IllegalStateException e) {
    return;
}

i to zrobiło. OK, nie mogę wyświetlić okna dialogowego, ale w tym przypadku było dobrze.

Było to jedyne miejsce w mojej aplikacji, w którym po raz pierwszy wywołałem FragmentManager.beginTransaction (), ale nigdy nie wywołałem commit (), więc nie znalazłem go, gdy szukałem „commit ()”.

Zabawne jest to, że użytkownik nigdy nie opuszcza aplikacji. Zamiast tego zabójcą była reklama pełnoekranowa AdMob.

Roger CS Wernersson
źródło
3
To samo tutaj. Rozwiązałem przesłonięcie metody „show (menedżer FragmentManager, znacznik String)”, zastępując „commit” metodą „commitAllowingStateLoss”; coś tracę, ponieważ nie mogę ustawić dwóch prywatnych atrybutów okna dialogowego: mDismissed i mShownByMe. Ale wydaje się, że działa za każdym razem :)
Francesco Ditrani
Stworzyłem alternatywne rozwiązanie dla DialogFragment, które pozwala uniknąć tego wyjątku: github.com/AndroidDeveloperLB/DialogShard
programista Androida
4

Moje rozwiązanie tego problemu było

W fragmentach dodaj metody:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    guideMapFragment = (SupportMapFragment)a.getSupportFragmentManager().findFragmentById(R.id.guideMap);
    guideMap = guideMapFragment.getMap();
    ...
}

@Override
public void onDestroyView() {
    SherlockFragmentActivity a = getSherlockActivity();
    if (a != null && guideMapFragment != null) {
        try {
            Log.i(LOGTAG, "Removing map fragment");
            a.getSupportFragmentManager().beginTransaction().remove(guideMapFragment).commit();
            guideMapFragment = null;
        } catch(IllegalStateException e) {
            Log.i(LOGTAG, "IllegalStateException on exit");
        }
    }
    super.onDestroyView();
}

Może być źle, ale nie mogłem znaleźć nic lepszego.

mc.dev
źródło
Trues .. wyłapanie wyjątku może uniknąć awarii aplikacji, ale problemy z zachowaniem takich fragmentów pozostają na ekranie lub nie można ich dodać.
Marcos Vasconcelos,
4
Przewijaj dalej. prawda jest tam
anil
4

Mam ten problem, ale myślę, że ten problem nie jest związany z zatwierdzaniem i zatwierdzaniemAllowStateLoss.

Poniższy komunikat śledzenia stosu i wyjątku dotyczy commit ().

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

Ale ten wyjątek został spowodowany przez onBackPressed ()

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(Unknown Source)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(Unknown Source)
at android.support.v4.app.FragmentActivity.onBackPressed(Unknown Source)

Wszystkie zostały spowodowane przez checkStateLoss ()

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

mStateSaved będzie prawdziwe po onSaveInstanceState.

Ten problem rzadko się zdarza. Nigdy nie napotkałem tego problemu. Nie mogę powtórzyć problemu.

znalazłem problem 25517

Mogło to nastąpić w następujących okolicznościach

  1. Klawisz Wstecz jest wywoływany po onSaveInstanceState, ale przed rozpoczęciem nowej aktywności.

  2. użyj onStop () w kodzie

Nie jestem pewien, na czym polega problem. Więc użyłem brzydkiego sposobu.

@Override
public void onBackPressed() {

    try{
        super.onBackPressed();
    }catch (IllegalStateException e){
        // can output some information here
        finish();
    }
}
oO_ox
źródło
Naprawdę nie rozwiązałem problemu, ale ten problem nie jest związany z commit i commitAllowStateLoss.
oO_ox
4

Mam ten sam problem w mojej aplikacji. Ten problem został rozwiązany przez wywołanie super.onBackPressed();poprzedniej klasy i wywołanie commitAllowingStateLoss()bieżącej klasy za pomocą tego fragmentu.

Piotr
źródło
2
Dziękuję Ci. To rozwiązanie rozwiązało problem uisng commitAllowingStateLoss()zamiastcommit()
Chintak Patel
Unikaj używania commitAllowingStateLoss () medium.com/@elye.project/…
swooby
3

onSaveInstance zostanie wywołany, jeśli użytkownik obróci ekran, aby mógł załadować zasoby związane z nową orientacją.

Możliwe, że ten użytkownik obrócił ekran, a następnie naciśnie przycisk Wstecz (ponieważ możliwe jest również, że ten użytkownik sfałszował swój telefon podczas korzystania z aplikacji)

dols
źródło
2
Chociaż zmiany konfiguracji (takie jak zmiany orientacji) mogą powodować ten wyjątek, nie są one główną przyczyną.
Alex Lockwood,
2

Ten sam problem ode mnie i po całodniowej analizie wszystkich artykułów, blogu i stackoverflow znalazłem proste rozwiązanie. W ogóle nie używaj saveInstanceState, jest to warunek z jednym wierszem kodu. Na kodzie fragmentu:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(null);
    .....
Tobia Caneschi
źródło
2

Dzieje się tak za każdym razem, gdy próbujesz załadować fragment, ale działanie zmieniło swój stan na onPause (). Dzieje się tak na przykład, gdy próbujesz pobrać dane i załadować je do działania, ale do czasu kliknięcia przycisku przez użytkownika przeniesiony do następnej aktywności.

Możesz to rozwiązać na dwa sposoby

Można użyć transakcji.commitAllowingStateLoss () zamiast transakcji.commit (), aby załadować fragment, ale może to spowodować utratę wykonanej operacji zatwierdzania.

lub

Upewnij się, że aktywność jest wznawiana i nie będzie wstrzymywać stanu podczas ładowania fragmentu. Utwórz wartość logiczną i sprawdź, czy aktywność nie przejdzie w stan onPause ().

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

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

następnie podczas ładowania fragmentu sprawdź, czy aktywność jest obecna i ładuj tylko, gdy aktywność jest na pierwszym planie.

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

Dzięki @gunar, ale myślę, że jest lepszy sposób.

Według dokumentu:

 * If you are committing a single transaction that does not modify the
 * fragment back stack, strongly consider using
 * {@link FragmentTransaction#commitNow()} instead. This can help avoid
 * unwanted side effects when other code in your app has pending committed
 * transactions that expect different timing.
 *
 * @return Returns true if there were any pending transactions to be
 * executed.
 */
public abstract boolean executePendingTransactions();

Więc użyj, commitNowaby zastąpić:

fragmentTransaction.commit();
FragmentManager.executePendingTransactions()
JianxinLi
źródło
0

Cóż, po wypróbowaniu wszystkich powyższych rozwiązań bez powodzenia (ponieważ w zasadzie nie mam transakcji).

W moim przypadku użyłem AlertDialogs i ProgressDialog jako fragmentów, które czasami przy rotacji, gdy pytamy o FragmentManager, błąd rośnie.

Znalazłem obejście mieszające kilka podobnych postów:

Jest to 3-etapowe rozwiązanie, wszystkie wykonane na FragmentActivity (w tym przypadku nazywa się GenericActivity):

private static WeakReference<GenericActivity> activity = null; //To avoid bug for fragments: Step 1 of 3

@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    //To avoid bug for fragments: Step 2 of 3
    activity = new WeakReference<GenericActivity>(this);
}

@Override
public FragmentManager getSupportFragmentManager(){
    //To avoid bug for fragments: Step 3 of 3
    if (this == activity.get()) {
        return super.getSupportFragmentManager();
    }
    return activity.get().getSupportFragmentManager();
}
Antylopa
źródło
0

Kiedy użyję startactivity w jednym fragmencie, otrzymam ten wyjątek;

Kiedy zmieniam się na startactivityforresult, wyjątek zniknął :)

Tak więc najłatwiejszym sposobem, aby to naprawić, jest użycie interfejsu startActivityForResult :)

Pytanie
źródło
0

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

DCS
źródło
0

Zostało to naprawione w Androidzie 4.2, a także w źródle biblioteki wsparcia. [*]

Szczegółowe informacje na temat przyczyny (i obejścia) można znaleźć w raporcie o błędach Google: http://code.google.com/p/android/issues/detail?id=19917

Jeśli korzystasz z biblioteki wsparcia, nie powinieneś martwić się tym błędem (na długo) [*]. Jeśli jednak używasz interfejsu API bezpośrednio (tj. Nie używasz FragmentManager biblioteki obsługi) i celujesz w interfejs API poniżej Androida 4.2, musisz wypróbować jedno z obejść tego problemu.

[*] W chwili pisania tego tekstu Menedżer pakietu SDK systemu Android nadal rozpowszechnia starą wersję, w której występuje ten błąd.

Edytuj Zamierzam tu dodać wyjaśnienie, ponieważ najwyraźniej w jakiś sposób zdezorientowałem tego, kto obniżył tę odpowiedź.

Istnieje kilka różnych (ale powiązanych) okoliczności, które mogą spowodować zgłoszenie tego wyjątku . Moja odpowiedź powyżej odnosi się do konkretnego wystąpienia omówionego w pytaniu, tj. Błędu w Androidzie, który został następnie naprawiony. Jeśli otrzymujesz ten wyjątek z innego powodu, to dlatego, że dodajesz / usuwasz fragmenty, kiedy nie powinieneś (po zapisaniu stanów fragmentów). Jeśli znajdujesz się w takiej sytuacji, być może przydadzą Ci się „ Zagnieżdżone fragmenty - IllegalStateException„ Nie można wykonać tej akcji po onSaveInstanceState ” ”.

Benjamin Dobell
źródło
0

Mój przypadek użycia: Użyłem detektora we fragmentach, aby powiadomić aktywność, że coś się wydarzyło. Zrobiłem nowy fragment zatwierdzenia metodą wywołania zwrotnego. Działa to doskonale za pierwszym razem. Ale po zmianie orientacji aktywność jest odtwarzana z zachowanym stanem instancji. W takim przypadku fragment nie jest ponownie tworzony, co oznacza, że ​​fragment ma detektora, który jest starą, zniszczoną działalnością. W dowolny sposób zostanie wywołana metoda wywołania zwrotnego po akcji. Chodzi o zniszczone działania, które powodują problem. Rozwiązaniem jest zresetowanie detektora we fragmentach z bieżącą aktywnością na żywo. To rozwiązuje problem.

Ganesh Kanna
źródło
0

Odkryłem, że jeśli inna aplikacja ma typ okna dialogowego i zezwala na wysyłanie poprawek do aplikacji w tle, prawie każda aplikacja w tle ulegnie awarii z powodu tego błędu. Myślę, że musimy za każdym razem sprawdzać, czy instancja została zapisana lub przywrócona.

Chris
źródło
0

W moim przypadku, z tym samym wyjątkiem błędu, umieszczam „onBackPressed ()” w uruchamialnym (możesz użyć dowolnego widoku):

myView.post(new Runnable() {
                    @Override
                    public void run() {
                        onBackPressed()
                    }
                });

Nie rozumiem dlaczego, ale działa!

Alecs
źródło
Publikowanie w widoku uruchomi Runnable dopiero po prawidłowym ułożeniu widoku i narysowaniu go na ekranie; często oznacza to, że sama działalność została w pełni wznowiona, a zatem nie ma problemów
Mercato
0

Możesz wywoływać fragmentManager.popBackStackImmediate (); kiedy aktywność jest wstrzymana. Działanie nie zostało zakończone, ale zostało wstrzymane i nie jest na pierwszym planie. Musisz sprawdzić, czy aktywność jest wstrzymana przed popBackStackImmediate ().

Murat
źródło
0

Zauważyłem coś bardzo interesującego. Mam w swojej aplikacji opcję otwarcia galerii telefonu, a urządzenie pyta, jakiej aplikacji użyć, tam klikam szary obszar poza oknem dialogowym i zobaczyłem ten problem. Zauważyłem, jak moja aktywność przechodzi z onPause, onSaveInstanceState z powrotem do onResume, nie zdarza się odwiedzać onCreateView. Robię transakcje w onResume. Skończyło się na ustawieniu flagi, która jest negowana naPause, ale jest prawdziwa naCreateView. jeśli flaga ma wartość true onResume, wykonaj onCommit, w przeciwnym razie commitAllowingStateLoss. Mogłem marnować tyle czasu, ale chciałem sprawdzić cykl życia. Mam urządzenie, które jest sdkversion 23, i nie dostaję tego problemu, ale mam inne, które ma 21, i tam widzę.

Juan Mendez
źródło
-1

możesz użyć FragmentActivity.onStart przed popBackStackImmediate

lubię to:

public void backStackFragment() {
    this.start();
    getFragmentManager().popBackStackImmediate();
}

public void start(){
    FragmentActivity a = getActivity();
    if(a instanceof DepositPlanPadActivity){
      ((DepositPlanPadActivity)a).onStart();
    }
    if(a instanceof SmallChangePlanPad){
            ((SmallChangePlanPad)a).onStart();
        }
        if(a instanceof UserCenterActivity){
            ((UserCenterActivity)a).onStart();
        }
    }

http://jorryliu.blogspot.com/2014/09/illegalstateexception-can-not-perform.html

użytkownik3327339
źródło