IllegalStateException: Diese Aktion kann nach onSaveInstanceState mit ViewPager nicht ausgeführt werden

496

Ich erhalte Benutzerberichte von meiner App auf dem Markt und erhalte die folgende Ausnahme:

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)

Anscheinend hat es etwas mit einem FragmentManager zu tun, den ich nicht benutze. Der Stacktrace zeigt keine meiner eigenen Klassen an, daher habe ich keine Ahnung, wo diese Ausnahme auftritt und wie ich sie verhindern kann.

Für die Aufzeichnung: Ich habe einen Tabhost, und in jedem Tab gibt es eine ActivityGroup, die zwischen Aktivitäten wechselt.

Nhaarman
quelle
2
Ich fand diese Frage über das gleiche Problem, aber auch keine Lösung. Stackoverflow.com/questions/7469082/…
Nhaarman
3
Während Sie nicht verwenden FragmentManager, ist Honeycomb sicherlich. Passiert das auf echten Honeycomb-Tabletten? Oder könnte es sein, dass jemand eine gehackte Wabe auf einem Telefon oder so etwas ausführt und es diese gehackte Edition ist, die Schwierigkeiten hat?
CommonsWare
1
Ich habe keine Ahnung. Dies ist die einzige Information, die ich in der Market Developer Console erhalte. Die Benutzermeldung enthält auch keine nützlichen Informationen.
Nhaarman
Ich verwende Flurry, das mir 11 Sitzungen mit Android 3.0.1 zeigt, und ich habe 11 Berichte über diese Ausnahme. Könnte aber Zufall sein. Android 3.1 und 3.2 haben 56 bzw. 38 Sitzungen.
Nhaarman
Der Marktfehlerbericht enthält einen Abschnitt "Plattform", manchmal enthält er die Android-Version des Geräts.
Nikolay Elenkov

Antworten:

720

Bitte überprüfen Sie meine Antwort hier . Grundsätzlich musste ich nur:

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

Rufen Sie super()die saveInstanceStateMethode nicht auf. Das hat die Dinge durcheinander gebracht ...

Dies ist ein bekannter Fehler im Support-Paket.

Wenn Sie die Instanz speichern und etwas zu Ihrer hinzufügen müssen, können outState BundleSie Folgendes verwenden:

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

Am Ende war die richtige Lösung (wie in den Kommentaren zu sehen) zu verwenden:

transaction.commitAllowingStateLoss();

beim Hinzufügen oder Ausführen des FragmentTransaction, das das verursachte Exception.

Ovidiu Latcu
quelle
370
Sie sollten commitAllowingStateLoss () anstelle von commit () verwenden
meh
18
Dieser Kommentar zu commitAllowingStateLoss () ist eine eigenständige Antwort - Sie sollten ihn so posten.
Risadinha
20
In Bezug auf 'commitAllowingStateLoss' - /> "Dies ist gefährlich, da das Commit verloren gehen kann, wenn die Aktivität später aus ihrem Status wiederhergestellt werden muss. Daher sollte dies nur in Fällen verwendet werden, in denen es in Ordnung ist, dass sich der UI-Status unerwartet ändert der Nutzer."
Codeversed
10
Wenn ich mir die v4-Quelle anschaue, popBackStackImmediateschlägt sie sofort fehl, wenn der Status gespeichert wurde. Das vorherige Hinzufügen des Fragments mit commitAllowingStateLossspielt keine Rolle. Meine Tests zeigen, dass dies wahr ist. Dies hat keine Auswirkungen auf diese spezielle Ausnahme. Was wir brauchen, ist eine popBackStackImmediateAllowingStateLossMethode.
Synesso
3
@ DanieleB ja, ich habe hier eine Antwort gepostet. Aber ich habe tatsächlich eine noch bessere Lösung gefunden, indem ich den Otto-Nachrichtenbus verwendet habe: Registrieren Sie das Fragment als Teilnehmer und warten Sie auf das asynchrone Ergebnis des Busses. Bei Pause die Registrierung aufheben und bei Wiederaufnahme erneut registrieren. Der Async benötigt auch eine Produce-Methode für die Zeiten, in denen er abgeschlossen ist und das Fragment angehalten wird. Wenn ich Zeit habe, werde ich meine Antwort damit detaillierter aktualisieren.
Synesso
130

Es gibt viele verwandte Probleme mit einer ähnlichen Fehlermeldung. Überprüfen Sie die zweite Zeile dieser bestimmten Stapelverfolgung. Diese Ausnahme bezieht sich speziell auf den Aufruf von FragmentManagerImpl.popBackStackImmediate.

Dieser Methodenaufruf, wie popBackStack, wird immer mit fehlschlagen , IllegalStateExceptionwenn der Sitzungszustand bereits gespeichert wurde. Überprüfen Sie die Quelle. Sie können nichts tun, um das Auslösen dieser Ausnahme zu verhindern.

  • Das Entfernen des Anrufs zu super.onSaveInstanceStatehilft nicht.
  • Das Erstellen des Fragments mit commitAllowingStateLosshilft nicht.

So habe ich das Problem beobachtet:

  • Es gibt ein Formular mit einer Senden-Schaltfläche.
  • Wenn Sie auf die Schaltfläche klicken, wird ein Dialogfeld erstellt und ein asynchroner Prozess gestartet.
  • Der Benutzer klickt auf die Home-Taste, bevor der Vorgang abgeschlossen ist. onSaveInstanceState wird aufgerufen.
  • Der Vorgang ist abgeschlossen, ein Rückruf wird durchgeführt und popBackStackImmediateversucht.
  • IllegalStateException ist geworfen.

Folgendes habe ich getan, um es zu lösen:

Da es nicht möglich ist, IllegalStateExceptionden Rückruf zu vermeiden , fangen und ignorieren Sie ihn.

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

Dies reicht aus, um den Absturz der App zu verhindern. Aber jetzt wird der Benutzer die App wiederherstellen und sehen, dass die Schaltfläche, die er gedrückt zu haben glaubte, überhaupt nicht gedrückt wurde (sie denken). Das Formularfragment wird noch angezeigt!

Um dies zu beheben, geben Sie beim Erstellen des Dialogfelds einen Status an, um anzuzeigen, dass der Prozess gestartet wurde.

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

Und speichern Sie diesen Status im Bundle.

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

Vergessen Sie nicht, es wieder in zu laden onViewCreated

Wenn Sie fortfahren, setzen Sie die Fragmente zurück, wenn zuvor versucht wurde, sie zu senden. Dies verhindert, dass der Benutzer zu einem scheinbar nicht übermittelten Formular zurückkehrt.

@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
quelle
5
Interessante Lektüre dazu hier: androiddesignpatterns.com/2013/08/…
Pascal
Wenn Sie DialogFragment verwenden, habe ich hier eine Alternative dazu gemacht: github.com/AndroidDeveloperLB/DialogShard
Android-Entwickler
Was ist, wenn das popBackStackImmediatevon Android selbst aufgerufen wurde?
Kimi Chiu
1
Absolut perfekt. Dies sollte die akzeptierte Antwort sein. Vielen Dank! Vielleicht würde ich submitPressed = false hinzufügen; nach popBackStackInmediate.
Neonigma
Ich habe die Methode public void onSaveInstanceState (Bundle outState) nicht verwendet. Muss ich eine leere Methode für public void onSaveInstanceState (Bundle outState) festlegen?
Faxriddin Abdullayev
55

Überprüfen Sie die Aktivität, isFinishing()bevor Sie das Fragment anzeigen, und achten Sie darauf commitAllowingStateLoss().

Beispiel:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}
Naskov
quelle
1
! isFinishing () &&! isDestroyed () funktioniert bei mir nicht.
Allen Vork
! isFinishing () &&! isDestroyed () hat bei mir funktioniert, erfordert jedoch API 17. Aber es wird einfach kein angezeigt DialogFragment. Siehe stackoverflow.com/questions/15729138/... für andere gute Lösungen, stackoverflow.com/a/41813953/2914140 mir geholfen.
CoolMind
29

Es ist Oktober 2017 und Google erstellt die Android Support Library mit der neuen Lifecycle-Komponente. Es enthält eine neue Idee für das Problem "Diese Aktion kann nach onSaveInstanceState nicht ausgeführt werden".

Zusamenfassend:

  • Verwenden Sie die Lebenszykluskomponente, um festzustellen, ob der Zeitpunkt für das Auftauchen Ihres Fragments korrekt ist.

Längere Version mit erklären:

  • Warum kommt dieses Problem heraus?

    Dies liegt daran, dass Sie versuchen, FragmentManageraus Ihrer Aktivität (die Ihr Fragment enthalten soll, nehme ich an?) Eine Transaktion für Ihr Fragment festzuschreiben. Normalerweise sieht es so aus, als würden Sie versuchen, eine Transaktion für ein bevorstehendes Fragment durchzuführen, während die Host-Aktivität bereits die savedInstanceStateMethode aufruft (der Benutzer kann zufällig die Home-Taste berühren, damit die Aktivität aufruft onStop(), in meinem Fall ist dies der Grund).

    Normalerweise sollte dieses Problem nicht auftreten - wir versuchen immer, das Fragment ganz am Anfang in die Aktivität zu laden, da die onCreate()Methode ein perfekter Ort dafür ist. Manchmal passiert dies jedoch , insbesondere wenn Sie nicht entscheiden können, welches Fragment Sie in diese Aktivität laden möchten, oder wenn Sie versuchen, ein Fragment aus einem AsyncTaskBlock zu laden (oder etwas etwas Zeit in Anspruch nimmt). Die Zeit, bevor die Fragmenttransaktion tatsächlich stattfindet, aber nach der onCreate()Methode der Aktivität kann der Benutzer alles tun. Wenn der Benutzer die Home-Taste drückt, wodurch die onSavedInstanceState()Methode der Aktivität can not perform this actionausgelöst wird , kommt es zu einem Absturz.

    Wenn jemand mehr über diese Ausgabe erfahren möchte, empfehle ich ihm, sich diesen Blog- Beitrag anzusehen . Es schaut tief in die Quellcodeschicht hinein und erklärt viel darüber. Es gibt auch den Grund, warum Sie die commitAllowingStateLoss()Methode nicht verwenden sollten, um diesen Absturz zu umgehen (glauben Sie mir, sie bietet nichts Gutes für Ihren Code).

  • Wie kann ich das beheben?

    • Soll ich die commitAllowingStateLoss()Methode zum Laden des Fragments verwenden? Nein, solltest du nicht ;

    • Sollte ich die onSaveInstanceStateMethode überschreiben , die darin enthaltene superMethode ignorieren ? Nein, solltest du nicht ;

    • Sollte ich die magische isFinishingInsider-Aktivität verwenden, um zu überprüfen, ob die Host-Aktivität zum richtigen Zeitpunkt für eine Fragmenttransaktion ist? Ja, das scheint der richtige Weg zu sein.

  • Schauen Sie sich an, welcher Lebenszyklus Komponente leisten kann.

    Grundsätzlich führt Google eine Implementierung innerhalb der AppCompatActivityKlasse durch (und mehrere andere Basisklassen, die Sie in Ihrem Projekt verwenden sollten), wodurch es einfacher wird, den aktuellen Lebenszyklusstatus zu bestimmen . Werfen Sie einen Blick zurück auf unser Problem: Warum sollte dieses Problem auftreten? Das liegt daran, dass wir etwas zum falschen Zeitpunkt tun. Also versuchen wir es nicht zu tun und dieses Problem wird verschwunden sein.

    Ich codiere ein wenig für mein eigenes Projekt, hier ist was ich benutze LifeCycle. Ich codiere in Kotlin.

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

Wie ich oben zeige. Ich werde den Lebenszyklusstatus der Hostaktivität überprüfen. Mit der Lifecycle-Komponente in der Support-Bibliothek könnte dies spezifischer sein. Der Code lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)bedeutet, wenn der aktuelle Status mindestens ist onResume, nicht später als er? Dies stellt sicher, dass meine Methode während eines anderen Lebenszustands (wie onStop) nicht ausgeführt wird.

  • Ist alles erledigt?

    Natürlich nicht. Der Code, den ich gezeigt habe, zeigt einen neuen Weg, um zu verhindern, dass die Anwendung abstürzt. Aber wenn es in den Zustand von geht onStop, wird diese Codezeile keine Dinge tun und somit nichts auf Ihrem Bildschirm anzeigen. Wenn Benutzer zur Anwendung zurückkehren, wird ein leerer Bildschirm angezeigt. Dies ist die leere Hostaktivität, bei der überhaupt keine Fragmente angezeigt werden. Es ist eine schlechte Erfahrung (ja, ein bisschen besser als ein Absturz).

    Hier wünschte ich mir, es könnte etwas Schöneres geben: Die App stürzt nicht ab, wenn es später um den Lebenszustand geht onResume Die Transaktionsmethode ist dem Lebenszustand bewusst. Außerdem wird die Aktivität versuchen, diese Fragmenttransaktionsaktion fortzusetzen, nachdem der Benutzer zu unserer App zurückgekehrt ist.

    Ich füge dieser Methode noch etwas hinzu:

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

Ich pflege eine Liste innerhalb dieser dispatcherKlasse, um diese Fragmente zu speichern, habe ich keine Chance, die Transaktionsaktion abzuschließen. Wenn der Benutzer vom Startbildschirm zurückkommt und feststellt, dass noch ein Fragment darauf wartet, gestartet zu werden, wird die resume()Methode unter der @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)Anmerkung aufgerufen. Jetzt denke ich, dass es so funktionieren sollte, wie ich es erwartet hatte.

Anthonyeef
quelle
8
Es wäre schön, Java anstelle von Kotlin
Shchvova
1
Warum verwendet Ihre Implementierung FragmentDispatchereine Liste zum Speichern ausstehender Fragmente, wenn nur ein Fragment wiederhergestellt wird?
Fraherm
21

Hier ist eine andere Lösung für dieses Problem.

Mit einer privaten Mitgliedsvariablen können Sie die zurückgegebenen Daten als Absicht festlegen, die dann nach super.onResume () verarbeitet werden kann.

Wie so:

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
quelle
7
Abhängig davon, welche Aktion Sie ausgeführt haben, die nicht zulässig war, muss dies möglicherweise zu einem noch späteren Zeitpunkt als onResume () verschoben werden. Wenn für insatcne FragmentTransaction.commit () das Problem ist, muss dies stattdessen in onPostResume () erfolgen.
pjv
1
Dies ist DIE Antwort auf diese Frage für mich. Da ich ein empfangenes NFC-Tag an die vorherige Aktivität weiterleiten musste, hat dies für mich getan.
Janis Peisenieks
7
Für mich geschah es, weil ich nicht anrief super.onActivityResult().
Sufian
20

Kurze und funktionierende Lösung:

Befolgen Sie einfache Schritte

Schritte

Schritt 1: Überschreiben Sie den onSaveInstanceStateStatus im jeweiligen Fragment. Und entfernen Sie die Super-Methode daraus.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Schritt 2: Verwenden fragmentTransaction.commitAllowingStateLoss( );

statt fragmentTransaction.commit( ); während Fragmentoperationen.

Vinayak
quelle
Die Antwort wird nicht kopiert oder in einer anderen Form weitergeleitet, in der .Ist veröffentlicht, um Menschen durch meine Arbeitslösung zu helfen, die durch mehrere Versuche und Irrtümer erhalten wurde
Vinayak
12

ACHTUNG , die Verwendung transaction.commitAllowingStateLoss()kann zu einer schlechten Erfahrung für den Benutzer führen. Weitere Informationen dazu, warum diese Ausnahme ausgelöst wird, finden Sie in diesem Beitrag .

Eric Brandwein
quelle
6
Dies gibt keine Antwort auf die Frage, Sie müssen eine gültige Antwort auf die Frage geben
Umar Ata
10

Ich habe eine schmutzige Lösung für diese Art von Problem gefunden. Wenn Sie Ihre ActivityGroupsaus irgendeinem Grund behalten möchten (ich hatte zeitliche Einschränkungen), implementieren Sie sie einfach

public void onBackPressed() {}

in Ihrem Activityund machen Sie einen backCode dort. Selbst wenn es auf älteren Geräten keine solche Methode gibt, wird diese Methode von neueren aufgerufen.

Saberrider
quelle
6

Verwenden Sie commitAllowingStateLoss () nicht. Es sollte nur in Fällen verwendet werden, in denen es in Ordnung ist, dass sich der UI-Status für den Benutzer unerwartet ändert.

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

Wenn die Transaktion in ChildFragmentManager von parentFragment ausgeführt wird, verwenden Sie stattdessen parentFragment.isResume () außerhalb, um dies zu überprüfen.

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}
Chandler
quelle
5

Ich hatte ein ähnliches Problem, das Szenario war wie folgt:

  • Meine Aktivität ist das Hinzufügen / Ersetzen von Listenfragmenten.
  • Jedes Listenfragment enthält einen Verweis auf die Aktivität, um die Aktivität zu benachrichtigen, wenn auf ein Listenelement geklickt wird (Beobachtermuster).
  • Jedes Listenfragment ruft auf Listenfragment setRetainInstance (true) auf. in seiner onCreate- Methode.

Das onCreate Methode der Aktivität war wie :

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

Die Ausnahme wurde ausgelöst, weil beim Ändern der Konfiguration (Gerät gedreht) die Aktivität erstellt wird, das Hauptfragment aus dem Verlauf des Fragmentmanagers abgerufen wird und gleichzeitig das Fragment bereits eine hat ALTEN Verweis auf die zerstörte Aktivität enthält

Durch Ändern der Implementierung wurde das Problem gelöst:

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

Sie müssen Ihre Listener jedes Mal festlegen, wenn die Aktivität erstellt wird, um zu vermeiden, dass die Fragmente Verweise auf alte zerstörte Instanzen der Aktivität enthalten.

Mina Samy
quelle
5

Wenn Sie von erben FragmentActivity, müssen Sie die Oberklasse aufrufen in onActivityResult():

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

Wenn Sie dies nicht tun und versuchen, in dieser Methode ein Fragmentdialogfeld anzuzeigen, erhalten Sie möglicherweise OPs IllegalStateException. (Um ehrlich zu sein, verstehe ich nicht ganz, warum der Superaufruf das Problem behebt. Er wurde onActivityResult()zuvor aufgerufen onResume(), daher sollte es immer noch nicht erlaubt sein, ein Fragmentdialogfeld anzuzeigen.)

Lawrence Kesteloot
quelle
1
Würde gerne wissen, warum dies das Problem behebt.
Big McLargeHuge
3

Ich habe diese Ausnahme erhalten, als ich die Zurück-Taste gedrückt habe, um die Absichtsauswahl für meine Kartenfragmentaktivität abzubrechen. Ich habe dieses Problem behoben, indem ich den Code von onResume (wo ich das Fragment initialisiert habe) durch onstart () ersetzt habe und die App funktioniert einwandfrei. Ich hoffe, es hilft.

DCS
quelle
2

Ich denke, die Verwendung transaction.commitAllowingStateLoss();ist nicht die beste Lösung. Diese Ausnahme wird ausgelöst, wenn die Konfiguration der Aktivität geändert und das Fragment onSavedInstanceState()aufgerufen wird und anschließend Ihre asynchrone Rückrufmethode versucht, ein Fragment festzuschreiben.

Eine einfache Lösung könnte darin bestehen, zu überprüfen, ob die Aktivität die Konfiguration ändert oder nicht

zB prüfen isChangingConfigurations()

dh

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

Kasse auch diesen Link

Amol Desai
quelle
Irgendwie habe ich diese Ausnahme bekommen, wenn der Benutzer auf etwas klickt (der Klick ist der Auslöser für das Transaktions-Commit). Wie konnte das sein? Würde Ihre Lösung hier hier sein?
Android-Entwickler
@androiddeveloper was machst du sonst noch beim Benutzerklick. Irgendwie speichert Fragment seinen Status, bevor Sie die Transaktion
festschreiben
Die Ausnahme wurde in der genauen Zeile des Transaktions-Commits ausgelöst. Außerdem hatte ich einen seltsamen Tippfehler: Anstelle von "hier hier" meinte ich "hier arbeiten".
Android-Entwickler
@androiddeveloper du hast recht! Aber bevor Sie eine Transaktion ausführen, erzeugen Sie einen Hintergrund-Thread oder etwas anderes?
Amol Desai
Ich glaube nicht (sorry, dass ich nicht im Büro bin), aber warum sollte es wichtig sein? Es ist alles UI-Zeug hier ... Wenn ich jemals etwas in einem Hintergrund-Thread machen würde, hätte ich dort Ausnahmen, und ich setze kein UI-bezogenes Zeug in Hintergrund-Threads, da es zu riskant ist.
Android-Entwickler
2

Möglicherweise bestand die reibungsloseste und einfachste Lösung, die ich in meinem Fall gefunden habe, darin, zu vermeiden, dass das fehlerhafte Fragment als Reaktion auf das Aktivitätsergebnis vom Stapel genommen wird. Also ändere diesen Anruf in meinem onActivityResult():

popMyFragmentAndMoveOn();

dazu:

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

hat in meinem Fall geholfen.

Mojuba
quelle
2

Wenn Sie in onActivityResult eine FragmentTransaction ausführen, können Sie in onActivityResult einen booleschen Wert festlegen. In onResume können Sie Ihre FragmentTransaction auf der Grundlage des booleschen Werts ausführen. Bitte beziehen Sie sich auf den Code unten.

@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
quelle
Bitte geben Sie den Code nicht als Bild ein, sondern verwenden Sie stattdessen die Code-Formatierung.
Micer
1
Ja, es hat mir geholfen. Dies ist die genaue und richtige Lösung für Marshmallow-Geräte. Ich habe diese Ausnahme erhalten und mit diesem einfachen Trick gelöst. !! Daher bis abgestimmt.
Sandhya Sasane
2

Mit freundlicher Genehmigung: Lösung für IllegalStateException

Dieses Problem hatte mich lange geärgert, aber zum Glück hatte ich eine konkrete Lösung dafür. Eine ausführliche Erklärung finden Sie hier .

Die Verwendung von commitAllowStateloss () könnte diese Ausnahme verhindern, würde jedoch zu Unregelmäßigkeiten der Benutzeroberfläche führen. Bisher haben wir verstanden, dass eine IllegalStateException auftritt, wenn wir versuchen, ein Fragment festzuschreiben, nachdem der Aktivitätsstatus verloren gegangen ist. Daher sollten wir die Transaktion nur verzögern, bis der Status wiederhergestellt ist Es kann einfach so gemacht werden

Deklarieren Sie zwei private boolesche Variablen

 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;

Jetzt setzen und deaktivieren wir in onPostResume () und onPause unsere boolesche Variable isTransactionSafe. Die Idee ist, die Transaktion nur dann als sicher zu markieren, wenn die Aktivität im Vordergrund steht, damit keine Gefahr eines Statusverlusts besteht.

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

-Was wir bisher getan haben, wird vor IllegalStateException gespeichert, aber unsere Transaktionen gehen verloren, wenn sie ausgeführt werden, nachdem die Aktivität in den Hintergrund verschoben wurde, ähnlich wie commitAllowStateloss (). Um dies zu unterstützen, haben wir die boolesche Variable 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
quelle
2

Fragmenttransaktionen sollten nicht danach ausgeführt werden Activity.onStop()! Stellen Sie sicher, dass Sie keine Rückrufe haben, nach denen eine Transaktion ausgeführt werden könnte onStop(). Es ist besser, den Grund zu beheben, als zu versuchen, das Problem mit Ansätzen wie zu umgehen.commitAllowingStateLoss()

Ivo Stoyanov
quelle
1

Ab der Support-Bibliothek Version 24.0.0 können Sie eine FragmentTransaction.commitNow()Methode aufrufen , die diese Transaktion synchron festschreibt, anstatt sie aufzurufen, commit()gefolgt von executePendingTransactions(). Wie die Dokumentation sagt, ist dieser Ansatz noch besser:

Das Aufrufen von commitNow ist dem Aufrufen von commit () gefolgt von executePendingTransactions () vorzuziehen, da letzteres den Nebeneffekt hat, dass versucht wird, alle derzeit ausstehenden Transaktionen festzuschreiben, unabhängig davon, ob dies das gewünschte Verhalten ist oder nicht.

Volodymyr
quelle
1

Wenn Sie versuchen, ein Fragment in Ihre Aktivität zu laden, stellen Sie sicher, dass die Aktivität wieder aufgenommen wird und nicht pausiert. Im Pausenstatus verlieren Sie möglicherweise den abgeschlossenen Festschreibungsvorgang.

Sie können transaction.commitAllowingStateLoss () anstelle von transaction.commit () verwenden, um das Fragment zu laden

oder

Erstellen Sie einen Booleschen Wert und prüfen Sie, ob die Aktivität nicht unterbrochen wird

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

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

dann beim Laden der Fragmentprüfung

if(mIsResumed){
//load the your fragment
}
Sharath Kumar
quelle
1

Um dieses Problem zu umgehen, können wir die Navigationsarchitekturkomponente verwenden , die in Google I / O 2018 eingeführt wurde. Die Navigationsarchitekturkomponente vereinfacht die Implementierung der Navigation in einer Android-App.

Levon Petrosyan
quelle
Es ist nicht gut genug und fehlerhaft (schlechtes Deeplink-Handling, Fragmente zum
Anzeigen
1

In Bezug auf @Anthonyeef gute Antwort, hier ist ein Beispielcode in Java:

private boolean shouldShowFragmentInOnResume;

private void someMethodThatShowsTheFragment() {

    if (this.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
        showFragment();
    } else {
        shouldShowFragmentInOnResume = true;
    }
}

private void showFragment() {
    //Your code here
}

@Override
protected void onResume() {
    super.onResume();

    if (shouldShowFragmentInOnResume) {
        shouldShowFragmentInOnResume = false;
        showFragment();
    }
}
MorZa
quelle
1

Wenn Sie mit der Methode popBackStack () oder popBackStackImmediate () abgestürzt sind, versuchen Sie es bitte mit:

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

Das funktioniert auch bei mir.

Tapa Speichern
quelle
Beachten Sie, dass es API 26 und höher erfordert
Itay Feldman
1

In meinem Fall habe ich diesen Fehler in einer Überschreibungsmethode namens onActivityResult erhalten. Nach dem Graben habe ich nur herausgefunden, dass ich vielleicht vorher ' super ' nennen musste.
Ich habe es hinzugefügt und es hat einfach funktioniert

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

}

Möglicherweise müssen Sie bei jeder Überschreibung, die Sie vor Ihrem Code ausführen, nur ein "Super" hinzufügen.

Gian Gomen
quelle
1

Kotlin Erweiterung

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

Verwendungszweck:

val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)
CoolMind
quelle
Sie können das () -> vor Fragment
Claire
@ Claire, danke! Meinst du wechseln zu fragment: Fragment? Ja, ich habe diese Variante ausprobiert, aber in diesem Fall wird in allen Fällen ein Fragment erstellt (auch wenn fragmentManager == null ist, aber ich bin auf diese Situation nicht gestoßen). Ich habe die Antwort aktualisiert und null geändert, um sie zu markieren addToBackStack().
CoolMind
1

Dieser Absturz ist darauf zurückzuführen, dass eine FragmentTransaction festgeschrieben wird, nachdem der Lebenszyklus der eigenen Aktivität bereits in SaveInstanceState ausgeführt wurde. Dies wird häufig durch das Festschreiben von FragmentTransactions aus einem asynchronen Rückruf verursacht. Weitere Informationen finden Sie in der verknüpften Ressource.

Fragmenttransaktionen & Aktivitätsstatusverlust

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

Bholendra Singh
quelle
0

Fügen Sie dies Ihrer Aktivität hinzu

@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
quelle
0

Ich habe auch dieses Problem festgestellt und das Problem tritt jedes Mal auf, wenn sich der Kontext von Ihnen FragmentActivityändert (z. B. wenn die Bildschirmausrichtung geändert wird usw.). Die beste Lösung dafür ist es, den Kontext von Ihrem zu aktualisieren FragmentActivity.

Adam
quelle
0

Am Ende habe ich ein Basisfragment erstellt und alle Fragmente in meiner App erweitern lassen

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

Wenn ich dann versuche, ein Fragment zu zeigen, verwende ich showAllowingStateLossanstelle vonshow

so was:

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

Ich bin aus dieser PR auf diese Lösung gekommen: https://github.com/googlesamples/easypermissions/pull/170/files

Ahmad El-Melegy
quelle
0

Eine weitere mögliche Problemumgehung, bei der ich nicht sicher bin, ob sie in allen Fällen hilfreich ist (Ursprung hier ):

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final View rootView = findViewById(android.R.id.content);
        if (rootView != null) {
            rootView.cancelPendingInputEvents();
        }
    }
}
Android-Entwickler
quelle
0

Ich weiß, dass es eine akzeptierte Antwort von @Ovidiu Latcu gibt, aber nach einiger Zeit bleibt der Fehler bestehen.

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

Crashlytics senden mir immer noch diese seltsame Fehlermeldung.

Allerdings tritt der Fehler jetzt nur bei Version 7+ (Nougat) auf. Mein Fix war die Verwendung von commitAllowingStateLoss () anstelle von commit () bei fragmentTransaction zu verwenden.

Dieser Beitrag ist hilfreich für commitAllowingStateLoss () und hatte nie wieder ein Fragmentproblem.

Zusammenfassend kann gesagt werden, dass die hier akzeptierte Antwort auf Android-Versionen vor Nougat funktioniert.

Dies kann jemandem einige Stunden der Suche ersparen. fröhliche Codierungen. <3 Prost

ralphgabb
quelle