579 Stimmen

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

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

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)

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

Für das Protokoll: Ich habe einen Tabhost, und in jedem Tab gibt es eine ActivityGroup, die zwischen den Activities wechselt.

778voto

Ovidiu Latcu Punkte 70281

Bitte überprüfen Sie meine Antwort aquí . Im Grunde musste ich nur :

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

Rufen Sie nicht an, um super() über die saveInstanceState Methode. Das hat alles durcheinander gebracht...

Dies ist eine bekannte Fehler im Support-Paket.

Wenn Sie die Instanz speichern müssen und etwas zu Ihrer outState Bundle können Sie Folgendes verwenden:

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

Letztendlich war die richtige Lösung (wie in den Kommentaren zu lesen) die Verwendung von :

transaction.commitAllowingStateLoss();

beim Hinzufügen oder Durchführen der FragmentTransaction die die Ursache für die Exception .

150voto

Synesso Punkte 35757

Es gibt viele verwandte Probleme mit einer ähnlichen Fehlermeldung. Prüfen Sie die zweite Zeile dieses speziellen Stacktrace. Diese Ausnahme bezieht sich speziell auf den Aufruf von FragmentManagerImpl.popBackStackImmediate .

Dieser Methodenaufruf, wie popBackStack wird immer durchfallen mit IllegalStateException wenn der Sitzungsstatus bereits gespeichert wurde. Überprüfen Sie die Quelle. Sie können nichts tun, um zu verhindern, dass diese Ausnahme ausgelöst wird.

  • Entfernen des Aufrufs von super.onSaveInstanceState wird nicht helfen.
  • Erstellen des Fragments mit commitAllowingStateLoss wird nicht helfen.

Ich habe das Problem folgendermaßen beobachtet:

  • Es gibt ein Formular mit einer Schaltfläche zum Absenden.
  • Wenn die Schaltfläche angeklickt wird, wird ein Dialogfeld erstellt und ein asynchroner Prozess gestartet.
  • Der Benutzer klickt auf die Home-Taste, bevor der Vorgang abgeschlossen ist - onSaveInstanceState genannt wird.
  • Der Prozess wird abgeschlossen, ein Rückruf wird durchgeführt und popBackStackImmediate versucht wird.
  • IllegalStateException geworfen wird.

Ich habe folgendes getan, um das Problem zu lösen:

Da es sich nicht vermeiden lässt, dass die IllegalStateException im Rückruf, fangen Sie es ab und ignorieren Sie es.

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

Das reicht aus, um einen Absturz der App zu verhindern. Aber jetzt wird der Benutzer die App wiederherstellen und sehen, dass die Taste, die er zu drücken glaubte, gar nicht gedrückt wurde (wie er glaubt). Das Formularfragment ist immer noch zu sehen!

Um dies zu beheben, sollten Sie bei der Erstellung des Dialogs einen Status festlegen, der anzeigt, dass der Prozess begonnen hat.

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

Und speichern Sie diesen Zustand im Bundle.

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

Vergessen Sie nicht, es wieder in onViewCreated

Bei der Wiederaufnahme werden die Fragmente zurückgesetzt, wenn zuvor versucht wurde, sie zu übermitteln. Dadurch wird verhindert, dass der Benutzer auf ein scheinbar nicht abgeschicktes Formular zurückgreift.

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

64voto

Naskov Punkte 4051

Prüfen Sie, ob die Aktivität isFinishing() bevor Sie das Fragment zeigen, und achten Sie auf 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();
}

41voto

Anthonyeef Punkte 2405

Es ist Oktober 2017, und Google macht Android Support Library mit den neuen Dingen nennen Lifecycle-Komponente. Es bietet einige neue Idee für diese "Kann nicht diese Aktion nach onSaveInstanceState" Problem durchführen.

Kurz gesagt:

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

Längere Version mit Erklärung:

  • Warum ist dieses Problem aufgetreten?

    Das liegt daran, dass Sie versuchen, die FragmentManager von Ihrer Aktivität (die Ihr Fragment enthalten wird, nehme ich an?), um eine Transaktion für Ihr Fragment zu bestätigen. Normalerweise würde dies so aussehen, dass Sie versuchen, eine Transaktion für ein kommendes Fragment durchzuführen, während die Host-Aktivität bereits savedInstanceState Methode (wenn der Benutzer die Home-Taste berührt, ruft die Aktivität onStop() (in meinem Fall ist das der Grund)

    Normalerweise sollte dieses Problem nicht auftreten -- wir versuchen immer, das Fragment ganz am Anfang in die Aktivität zu laden, wie die onCreate() Methode ist ein perfekter Ort dafür. Aber manchmal ist dies geschehen zu laden, vor allem, wenn Sie sich nicht entscheiden können, welches Fragment Sie in diese Aktivität laden wollen, oder wenn Sie versuchen, ein Fragment aus einer AsyncTask Block (oder irgendetwas wird ein wenig Zeit in Anspruch nehmen). Die Zeit, bevor die Fragmenttransaktion wirklich stattfindet, aber nachdem die Aktivität onCreate() Methode kann der Benutzer alles tun. Wenn der Benutzer die Home-Taste drückt, was die Aktivität auslöst onSavedInstanceState() Methode, würde es eine can not perform this action Absturz.

    Wer sich eingehender mit diesem Thema befassen möchte, dem empfehle ich einen Blick auf diesen Blog zu werfen Beitrag . Er blickt tief in die Quellcode-Ebene und erklärt viel darüber. Außerdem wird der Grund genannt, warum man nicht die commitAllowingStateLoss() Methode zur Umgehung dieses Absturzes (glauben Sie mir, es bringt nichts Gutes für Ihren Code)

  • Wie kann man das beheben?

    • Soll ich die commitAllowingStateLoss() Methode zum Laden von Fragmenten? Nein, das sollten Sie nicht. ;

    • Soll ich die onSaveInstanceState Methode, ignorieren super Methode darin? Nein, das sollten Sie nicht. ;

    • Soll ich die magische isFinishing innerhalb der Aktivität, um zu prüfen, ob die Host-Aktivität zum richtigen Zeitpunkt für die Fragmenttransaktion erfolgt? Ja, das sieht aus wie den richtigen Weg zu gehen.

  • Schauen Sie sich an, was Lebenszyklus Komponente tun kann.

    Grundsätzlich macht Google eine Implementierung innerhalb der AppCompatActivity Klasse (und mehrere andere Basisklassen, die Sie in Ihrem Projekt verwenden sollten), was es einfacher macht, die den aktuellen Stand des Lebenszyklus ermitteln . Schauen wir zurück zu unserem Problem: Warum sollte dieses Problem auftreten? Es liegt daran, dass wir etwas zum falschen Zeitpunkt tun. Also versuchen wir, es nicht zu tun, und das Problem wird verschwinden.

    Ich programmiere ein wenig für mein eigenes Projekt, hier ist, was ich tun mit LifeCycle . Ich programmiere 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 gezeigt habe. Ich werde den Lebenszyklus-Status der Host-Aktivitä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 Zustand mindestens onResume und nicht später als sie? Was sicherstellt, dass meine Methode nicht während eines anderen Lebenszustands ausgeführt wird (wie onStop ).

  • Ist alles erledigt?

    Nein, natürlich nicht. Der Code, den ich gezeigt habe, zeigt einen neuen Weg, um zu verhindern, dass die Anwendung abstürzt. Aber wenn sie in den Zustand von onStop wird diese Codezeile nicht funktionieren und somit nichts auf dem Bildschirm anzeigen. Wenn die Benutzer zur Anwendung zurückkehren, werden sie einen leeren Bildschirm sehen, da die leere Hostaktivität keine Fragmente anzeigt. Es ist eine schlechte Erfahrung (ja, ein bisschen besser als ein Absturz).

    Hier würde ich mir also etwas Schöneres wünschen: Die App wird nicht abstürzen, wenn sie später als onResume Die Transaktionsmethode ist sich des Lebenszustands bewusst; außerdem wird die Aktivität versuchen, das Fragment der Transaktionsaktion zu beenden, nachdem der Benutzer zu unserer Anwendung zurückgekehrt ist.

    Ich füge noch etwas zu dieser Methode 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 führe eine Liste innerhalb dieser dispatcher Klasse, um die Fragmente zu speichern, die keine Chance haben, die Transaktion zu beenden. Und wenn der Benutzer vom Startbildschirm zurückkommt und feststellt, dass noch ein Fragment darauf wartet, gestartet zu werden, wird es in der resume() Methode unter dem @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) Bemerkung. Ich denke, jetzt sollte es so funktionieren, wie ich es erwartet habe.

22voto

Vinayak Punkte 5668

Kurze und funktionierende Lösung :

Folgen Sie einfachen Schritten

Schritte

Schritt 1: Außerkraftsetzen onSaveInstanceState Zustand im jeweiligen Fragment. Und entfernen Sie die Super-Methode aus ihm.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Schritt 2: Verwenden Sie fragmentTransaction.commitAllowingStateLoss( );

anstelle von fragmentTransaction.commit( ); während Fragmentierungsvorgängen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X