Beim Anzeigen des Dialogfelds wird angezeigt, dass diese Aktion nach onSaveInstanceState nicht ausgeführt werden kann.

121

Einige Benutzer melden, wenn sie die Schnellaktion in der Benachrichtigungsleiste verwenden, wird eine Truppe geschlossen.

Ich zeige eine schnelle Aktion in der Benachrichtigung, die die Klasse "TestDialog" aufruft . In der TestDialog-Klasse wird nach Drücken der Schaltfläche "Snooze" der SnoozeDialog angezeigt.

private View.OnClickListener btnSnoozeOnClick() {
    return new View.OnClickListener() {

        public void onClick(View v) {
            showSnoozeDialog();
        }
    };
}

private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    snoozeDialog.show(fm, "snooze_dialog");
}

Der Fehler ist *IllegalStateException: Can not perform this action after onSaveInstanceState*.

Die Codezeile, in der die IllegarStateException ausgelöst wird, lautet:

snoozeDialog.show(fm, "snooze_dialog");

Die Klasse erweitert "FragmentActivity" und die "SnoozeDialog" -Klasse erweitert "DialogFragment".

Hier ist die vollständige Stapelverfolgung des Fehlers:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1338)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
at android.support.v4.app.DialogFragment.show(DialogFragment.java:127)
at com.test.testing.TestDialog.f(TestDialog.java:538)
at com.test.testing.TestDialog.e(TestDialog.java:524)
at com.test.testing.TestDialog.d(TestDialog.java:519)
at com.test.testing.g.onClick(TestDialog.java:648)
at android.view.View.performClick(View.java:3620)
at android.view.View$PerformClick.run(View.java:14292)
at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4507)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
at dalvik.system.NativeStart.main(Native Method)

Ich kann diesen Fehler nicht reproduzieren, erhalte jedoch viele Fehlerberichte.

Kann mir jemand helfen, wie kann ich diesen Fehler beheben?

Chrisonline
quelle
2
Haben Sie eine Lösung gefunden? Ich habe das gleiche Problem wie du. Ich habe hier gefragt: stackoverflow.com/questions/15730878/… Bitte überprüfen Sie meine Frage und sehen Sie die mögliche Lösung, die für meinen Fall nicht funktioniert. Vielleicht funktioniert es für Sie.
Rootpanthera
Noch keine Lösung :-( Und Ihr Vorschlag wurde bereits zu meiner Klasse hinzugefügt.
chrisonline
Überprüfen Sie die akzeptierte Antwort von hier. Dies löste mein Problem: stackoverflow.com/questions/14177781/...
bogdan
4
Ist Ihre Aktivität sichtbar, wenn dieser Dialog ausgelöst wird? Dies scheint darauf zurückzuführen zu sein, dass Ihre App versucht, ein Dialogfeld anzuzeigen, das an eine Aktivität angehängt ist, die angehalten / gestoppt wurde.
Kai
stackoverflow.com/questions/7575921/… Sie haben dies versucht Ich bin sicher.
Orion

Antworten:

66

Dies ist ein häufiges Problem . Wir haben dieses Problem gelöst, indem wir show () überschrieben und Ausnahmen in der erweiterten Klasse DialogFragment behandelt haben

public class CustomDialogFragment extends DialogFragment {

    @Override
    public void show(FragmentManager manager, String tag) {
        try {
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        } catch (IllegalStateException e) {
            Log.d("ABSDIALOGFRAG", "Exception", e);
        }
    }
}

Beachten Sie, dass durch das Anwenden dieser Methode die internen Felder der DialogFragment.class nicht geändert werden:

boolean mDismissed;
boolean mShownByMe;

Dies kann in einigen Fällen zu unerwarteten Ergebnissen führen. Verwenden Sie besser commitAllowingStateLoss () anstelle von commit ()

Rafael
quelle
3
Aber warum tritt dieses Problem auf? Ist es in Ordnung, den Fehler zu ignorieren? Was passiert, wenn du es tust? Wenn Sie auf klicken, bedeutet dies, dass die Aktivität aktiv und gut ist. Wie auch immer, ich habe hier darüber berichtet, weil ich dies als Fehler betrachte: code.google.com/p/android/issues/detail?id= 207269
Android-Entwickler
1
Können Sie dort bitte die Hauptrolle spielen und / oder Kommentare abgeben?
Android-Entwickler
2
Es ist besser, super.show (Manager, Tag) in der try-catch-Klausel aufzurufen. Die Flaggen von DialogFragment können auf diese Weise sicher bleiben
Shayan_Aryan
19
Zu diesem Zeitpunkt können Sie commitAllowingStateLoss () anstelle von commit () aufrufen. Die Ausnahme würde nicht erhoben.
ARLabs
1
@ARLabs Ich stelle mir in diesem Fall vor, dass dies auch für den Antwortenden besser wäre, da der Dialog definitiv nicht angezeigt wird, wenn Sie nur die hier gezeigte Ausnahme abfangen. Es ist besser, den Dialog anzuzeigen, wenn Sie können, und er ist möglicherweise nicht mehr vorhanden, wenn der Status wiederhergestellt werden muss. Halten Sie außerdem die Nutzung Ihres App-Speichers im Hintergrund niedrig, damit er wahrscheinlich nicht zerstört wird.
AndroidGy
27

Das bedeutet, dass Sie commit()( show()im Fall von DialogFragment) nach fragmentieren onSaveInstanceState().

Android speichert Ihren Fragmentstatus unter onSaveInstanceState(). Wenn Sie also commit()fragmentieren, geht der onSaveInstanceState()Fragmentstatus verloren.

Wenn Aktivität beendet wird und später neu erstellt wird, wird das Fragment daher nicht zu Aktivitäten hinzugefügt, die eine schlechte Benutzererfahrung darstellen. Deshalb erlaubt Android nicht um jeden Preis den Verlust des Staates.

Die einfache Lösung besteht darin, zu überprüfen, ob der Status bereits gespeichert ist.

boolean mIsStateAlreadySaved = false;
boolean mPendingShowDialog = false;

@Override
public void onResumeFragments(){
    super.onResumeFragments();
    mIsStateAlreadySaved = false;
    if(mPendingShowDialog){
        mPendingShowDialog = false;
        showSnoozeDialog();
    }
}

@Override
public void onPause() {
    super.onPause();
    mIsStateAlreadySaved = true;
}

private void showSnoozeDialog() {
    if(mIsStateAlreadySaved){
        mPendingShowDialog = true;
    }else{
        FragmentManager fm = getSupportFragmentManager();
        SnoozeDialog snoozeDialog = new SnoozeDialog();
        snoozeDialog.show(fm, "snooze_dialog");
    }
}

Hinweis: onResumeFragments () wird aufgerufen, wenn Fragmente wieder aufgenommen werden.

Pongpat
quelle
1
Was ist, wenn ich das DialogFragment in einem anderen Fragment anzeigen möchte?
Android-Entwickler
Unsere Lösung besteht darin, Aktivität und Fragment-Basisklasse zu erstellen und onResumeFragments an Fragment zu delegieren (wir erstellen onResumeFragments in Fragment-Basisklasse). Es ist keine schöne Lösung, aber es funktioniert. Wenn Sie eine bessere Lösung haben, lassen Sie es mich bitte wissen :)
Pongpat
Nun, ich dachte, dass das Anzeigen des Dialogfelds im "onStart" gut funktionieren sollte, da das Fragment sicherlich angezeigt wird, aber ich sehe immer noch einige Absturzberichte darüber. Ich wurde angewiesen, es stattdessen auf "onResume" zu setzen. Über Alternativen habe ich Folgendes gesehen: twigstechtips.blogspot.co.il/2014/01/… , aber es ist ziemlich seltsam.
Android-Entwickler
Ich denke, der Grund, warum twigstechtips.blogspot.co.il/2014/01/… funktioniert, weil es einen neuen Thread startet und daher den gesamten Lebenszykluscode, dh onStart, onResume usw., der aufgerufen wurde, bevor der Code runOnUiThread jemals ausgeführt wurde. Das bedeutet, dass der Status bereits vor dem Aufruf von runOnUiThread wiederhergestellt wurde.
Pongpat
2
Ich benutze einen einzelnen Anruf zum Posten (lauffähig). In Bezug auf getFragmentManager kommt es darauf an. Wenn Sie diesen Dialog mit einer anderen Aktivität teilen möchten, sollten Sie getFragmentManager verwenden. Wenn dieser Dialog jedoch nur mit dem Fragment getChildFragmentManager vorhanden ist, scheint dies die bessere Wahl zu sein.
Pongpat
16
private void showSnoozeDialog() {
    FragmentManager fm = getSupportFragmentManager();
    SnoozeDialog snoozeDialog = new SnoozeDialog();
    // snoozeDialog.show(fm, "snooze_dialog");
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(snoozeDialog, "snooze_dialog");
    ft.commitAllowingStateLoss();
}

ref: link

huu duy
quelle
11

Nach wenigen Tagen möchte ich meine Lösung teilen , wie ich es behoben haben, DialogFragment Sie sollten zeigen , außer Kraft zu setzen show()Methode davon und rufen commitAllowingStateLoss()auf TransactionObjekt. Hier ist ein Beispiel in Kotlin:

override fun show(manager: FragmentManager?, tag: String?) {
        try {
            val ft = manager?.beginTransaction()
            ft?.add(this, tag)
            ft?.commitAllowingStateLoss()
        } catch (ignored: IllegalStateException) {

        }

    }
Dennis Zinkovski
quelle
1
Damit Entwickler nicht von DialogFragmentIhnen erben müssen, können Sie dies in eine Kotlin-Erweiterungsfunktion mit der folgenden Signatur ändern : fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String). Außerdem ist der Try-Catch nicht erforderlich, da Sie die commitAllowingStateLoss()Methode und nicht die commit()Methode aufrufen .
Adil Hussain
10

Wenn der Dialog nicht wirklich wichtig ist (es ist in Ordnung, ihn nicht anzuzeigen, wenn die App geschlossen wird / nicht mehr angezeigt wird), verwenden Sie:

boolean running = false;

@Override
public void onStart() {
    running = true;
    super.onStart();
}

@Override
public void onStop() {
    running = false;
    super.onStop();
}

Und öffnen Sie Ihren Dialog (Fragment) nur, wenn wir laufen:

if (running) {
    yourDialog.show(...);
}

BEARBEITEN, MÖGLICHERWEISE BESSERE LÖSUNG:

Wo onSaveInstanceState im Lebenszyklus aufgerufen wird, ist unvorhersehbar. Ich denke, eine bessere Lösung besteht darin, isSavedInstanceStateDone () wie folgt zu überprüfen:

/**
 * True if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
private boolean savedInstanceStateDone;

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

    savedInstanceStateDone = false;
}

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

    savedInstanceStateDone = false;
}

protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    savedInstanceStateDone = true;
}


/**
 * Returns true if SavedInstanceState was done, and activity was not restarted or resumed yet.
 */
public boolean isSavedInstanceStateDone() {
    return savedInstanceStateDone;
}
Frank
quelle
Dies scheint nicht zu funktionieren, da diese Ausnahme beim Methodenaufruf "onStart" auftritt (wobei versucht wird, das DialogFragment dort anzuzeigen).
Android-Entwickler
Du hast meinen Tag gerettet. Danke Frank.
Cüneyt
6

Bitte versuchen Sie, FragmentTransaction anstelle von FragmentManager zu verwenden. Ich denke, der folgende Code wird Ihr Problem lösen. Wenn nicht, lassen Sie es mich bitte wissen.

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
SnoozeDialog snoozeDialog = new SnoozeDialog();
snoozeDialog.show(ft, "snooze_dialog");

BEARBEITEN:

Fragmenttransaktion

Bitte überprüfen Sie diesen Link. Ich denke, es wird Sie Fragen lösen.

RIJO RV
quelle
4
Jede Erklärung, warum die Verwendung von FragmentTransaction das Problem behebt, wäre großartig.
Hemanshu
3
Dialog # show (FragmentManager, Tag) macht dasselbe. Dies ist keine Lösung.
William
3
Diese Antwort ist nicht die Lösung. DialogFragment # show (ft) und show (fm) machen genau dasselbe.
Danijoo
@danijoo Du hast Recht, dass beide den gleichen Job machen. Bei einigen Telefonen tritt jedoch ein ähnliches Problem auf, wenn Sie den Fragmentmanager anstelle der Fragmenttransaktion verwenden. In meinem Fall löste dies mein Problem.
RIJO RV
6

Ich bin seit Jahren auf dieses Problem gestoßen.
Das Internet ist übersät mit Dutzenden (Hunderttausenden?) Diskussionen darüber, und Verwirrung und Desinformation in ihnen scheinen in Hülle und Fülle vorhanden zu sein.
Um die Situation noch schlimmer zu machen und im Geiste des xkcd-Comics "14 Standards", werfe ich meine Antwort in den Ring.
xkcd 14 Standards

Die cancelPendingInputEvents(), commitAllowingStateLoss(), catch (IllegalStateException e)und ähnliche Lösungen scheinen alle scheußlich.

Hoffentlich zeigt das Folgende leicht, wie das Problem reproduziert und behoben werden kann:

private static final Handler sHandler = new Handler();
private boolean mIsAfterOnSaveInstanceState = true;

@Override
protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    mIsAfterOnSaveInstanceState = true; // <- To repro, comment out this line
}

@Override
protected void onPostResume()
{
    super.onPostResume();
    mIsAfterOnSaveInstanceState = false;
}

@Override
protected void onResume()
{
    super.onResume();
    sHandler.removeCallbacks(test);
}

@Override
protected void onPause()
{
    super.onPause();
    sHandler.postDelayed(test, 5000);
}

Runnable test = new Runnable()
{
    @Override
    public void run()
    {
        if (mIsAfterOnSaveInstanceState)
        {
            // TODO: Consider saving state so that during or after onPostResume a dialog can be shown with the latest text
            return;
        }

        FragmentManager fm = getSupportFragmentManager();
        DialogFragment dialogFragment = (DialogFragment) fm.findFragmentByTag("foo");
        if (dialogFragment != null)
        {
            dialogFragment.dismiss();
        }

        dialogFragment = GenericPromptSingleButtonDialogFragment.newInstance("title", "message", "button");
        dialogFragment.show(fm, "foo");

        sHandler.postDelayed(test, 5000);
    }
};
swooby
quelle
1
Ich liebe Leute, die ohne Erklärung abstimmen. Anstatt nur abzustimmen, wäre es vielleicht besser, wenn sie erklären, wie meine Lösung fehlerhaft ist? Kann ich die Abwahl eines Downwählers ablehnen?
Swooby
1
Ja, es ist ein Problem von SO, ich schreibe dieses Problem jedes Mal in Vorschlägen, aber sie wollen es nicht lösen.
CoolMind
2
Ich denke, dass Abstimmungen ein Ergebnis der eingebetteten XKCD sein können. Antworten sind wirklich nicht der richtige Ort für soziale Kommentare (egal wie lustig und / oder wahr).
RestingRobot
5

Die Verwendung der neuen Lebenszyklusbereiche von Activity-KTX ist so einfach wie das folgende Codebeispiel:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

Diese Methode kann direkt nach onStop () aufgerufen werden und zeigt den Dialog erfolgreich an, sobald onResume () bei der Rückkehr aufgerufen wurde.

PenguinDan
quelle
3

In vielen Ansichten werden Ereignisse auf hoher Ebene, z. B. Klick-Handler, in die Ereigniswarteschlange gestellt, um sie verzögert auszuführen. Das Problem ist also, dass "onSaveInstanceState" bereits für die Aktivität aufgerufen wurde, die Ereigniswarteschlange jedoch ein verzögertes "Klickereignis" enthält. Daher, wenn dieses Ereignis an Ihren Handler gesendet wird

at android.os.Handler.handleCallback(Handler.java:605)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)

und Ihr Code führt showdie IllegalStateException aus.

Die einfachste Lösung besteht darin, die Ereigniswarteschlange in zu bereinigen onSaveInstanceState

protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // ..... do some work
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            findViewById(android.R.id.content).cancelPendingInputEvents();
        }
}
sim
quelle
Haben Sie tatsächlich bestätigt, dass dies das Problem löst?
Mhsmith
Google hat dies zur nächsten Version der Androidx-Bibliotheken hinzugefügt, die sich derzeit in der Beta ( activityund fragment) befinden.
Mhsmith
1
@mhsmith Ich erinnere mich, dass diese Lösung das Problem in meinem Code mit IllegalStateException gelöst hat
sim
2

Machen Sie Ihr Dialogfragmentobjekt global und rufen Sie entlassenAllowingStateLoss () in der Methode onPause () auf

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

    if (dialogFragment != null) {
        dialogFragment.dismissAllowingStateLoss();
    }
}

Vergessen Sie nicht, den Wert in Fragment zuzuweisen und show () beim Klicken auf die Schaltfläche oder wo auch immer aufzurufen.

Rohit Rajpal
quelle
1

Es wird zwar nirgendwo offiziell erwähnt, aber ich habe mich ein paar Mal mit diesem Problem befasst. Nach meiner Erfahrung stimmt etwas in der Kompatibilitätsbibliothek nicht, die Fragmente auf älteren Plattformen unterstützt, was dieses Problem verursacht. Sie verwenden dies, indem Sie die normale Fragmentmanager-API verwenden. Wenn nichts funktioniert, können Sie den normalen Dialog anstelle des Dialogfragments verwenden.

Dalvinder Singh
quelle
1
  1. Fügen Sie diese Klasse Ihrem Projekt hinzu: (muss sich im Paket android.support.v4.app befinden )
Paket android.support.v4.app;


/ **
 * Erstellt von Gil am 16.08.2017.
 * /

öffentliche Klasse StatelessDialogFragment erweitert DialogFragment {
    / **
     * Zeigen Sie den Dialog an, fügen Sie das Fragment unter Verwendung einer vorhandenen Transaktion hinzu und schreiben Sie dann das fest
     * Transaktion unter Berücksichtigung des Statusverlusts.
* * * Ich würde empfehlen, dass Sie die meiste Zeit aber {@link #show (FragmentTransaction, String)} verwenden * Dies ist für Dialoge gedacht, die Ihnen wirklich egal sind. (Debug / Tracking / Werbung etc.) * * * @param Transaktion * Eine vorhandene Transaktion, in der das Fragment hinzugefügt werden soll. * @param tag * Das Tag für dieses Fragment gemäß * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @return Gibt die Kennung der festgeschriebenen Transaktion gemäß zurück * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentManager, String) * / public int showAllowingStateLoss (FragmentTransaction-Transaktion, String-Tag) { mDismissed = false; mShownByMe = true; transaction.add (this, tag); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss (); return mBackStackId; }} / ** * Zeigen Sie den Dialog an und fügen Sie das Fragment dem angegebenen FragmentManager hinzu. Dies ist eine Annehmlichkeit * zum expliziten Erstellen einer Transaktion, Hinzufügen des Fragments mit dem angegebenen Tag und * es zu begehen, ohne sich um den Staat zu kümmern. Dadurch wird die Transaktion nicht zum hinzugefügt * Rückstapel. Wenn das Fragment verworfen wird, wird eine neue Transaktion ausgeführt, um es zu entfernen * aus der Aktivität.
* * * Ich würde empfehlen, dass Sie die meiste Zeit {@link #show (FragmentManager, String)} verwenden, aber das ist * für Dialoge, die Sie wirklich nicht interessieren. (Debug / Tracking / Werbung etc.) * * * * * @param Manager * Der FragmentManager, zu dem dieses Fragment hinzugefügt wird. * @param tag * Das Tag für dieses Fragment gemäß * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) * / public void showAllowingStateLoss (FragmentManager-Manager, String-Tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction (); ft.add (this, tag); ft.commitAllowingStateLoss (); }} }}
  1. Erweitern Sie StatelessDialogFragment anstelle von DialogFragment
  2. Verwenden Sie die Methode showAllowingStateLoss anstelle von show

  3. Genießen ;)

Gil SH
quelle
Wofür sind diese booleschen Felder gedacht? Warum werden sie nicht als Klassenmitglieder deklariert?
undefiniert
1
Die booleschen Felder sind geschützte Mitglieder von DialogFragment, ihre Namen legen offensichtlich nahe, wofür sie bestimmt sind, und wir müssen sie aktualisieren, um die Logik von DialogFragment nicht zu beeinträchtigen. Beachten Sie, dass diese Funktionen in der ursprünglichen DialogFragment-Klasse vorhanden sind, jedoch ohne öffentlichen Zugriff
Gil SH
Obwohl diese Mitglieder nicht geschützt sind, sind sie intern. Ich habe Kompilierungsfehler erhalten, als ich sie StatelessDialogFragmentin eines meiner Pakete gesteckt habe. Danke, Alter. Ich werde es bald in der Produktion testen.
undefiniert
1

Verwenden Sie diesen Code

FragmentTransaction ft = fm.beginTransaction();
        ft.add(yourFragment, "fragment_tag");
        ft.commitAllowingStateLoss();

anstatt

yourFragment.show(fm, "fragment_tag");
Faxriddin Abdullayev
quelle
1

Ich habe eine elegante Lösung für dieses Problem gefunden, indem ich Reflexion verwendet habe. Das Problem aller oben genannten Lösungen ist, dass die Felder mDismissed und mShownByMe ihren Status nicht ändern.

Überschreiben Sie einfach die Methode "show" in Ihrem eigenen benutzerdefinierten Dialogfragment für das untere Blatt, wie im folgenden Beispiel (Kotlin).

override fun show(manager: FragmentManager, tag: String?) {
        val mDismissedField = DialogFragment::class.java.getDeclaredField("mDismissed")
        mDismissedField.isAccessible = true
        mDismissedField.setBoolean(this, false)

        val mShownByMeField = DialogFragment::class.java.getDeclaredField("mShownByMe")
        mShownByMeField.isAccessible = true
        mShownByMeField.setBoolean(this, true)

        manager.beginTransaction()
                .add(this, tag)
                .commitAllowingStateLoss()
    }
Рома Богдан
quelle
3
"Ich habe eine elegante Lösung für dieses Problem gefunden, indem ich Reflexion verwendet habe." wie ist es elegant
Mark Buikema
elegant, stilvoll, schick, klug, nett, anmutig
Febома Богдан
1
Es ist die einzige Lösung, die für mich funktioniert hat. Ich finde es elegant
MBH
0

Die folgende Implementierung kann verwendet werden, um das Problem der Durchführung sicherer Statusänderungen während des ActivityLebenszyklus zu lösen , insbesondere um Dialoge anzuzeigen: Wenn der Instanzstatus bereits gespeichert wurde (z. B. aufgrund einer Konfigurationsänderung), werden sie verschoben, bis der wieder aufgenommene Status vorliegt durchgeführt wurde.

public abstract class XAppCompatActivity extends AppCompatActivity {

    private String TAG = this.getClass().getSimpleName();

    /** The retained fragment for this activity */
    private ActivityRetainFragment retainFragment;

    /** If true the instance state has been saved and we are going to die... */
    private boolean instanceStateSaved;

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // get hold of retain Fragment we'll be using
        retainFragment = ActivityRetainFragment.get(this, "Fragment-" + this.getClass().getName());
    }

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

        // reset instance saved state
        instanceStateSaved = false;

        // execute all the posted tasks
        for (ActivityTask task : retainFragment.tasks) task.exec(this);
        retainFragment.tasks.clear();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        instanceStateSaved = true;
    }

    /**
     * Checks if the activity state has been already saved.
     * After that event we are no longer allowed to commit fragment transactions.
     * @return true if the instance state has been saved
     */
    public boolean isInstanceStateSaved() {
        return instanceStateSaved;
    }

    /**
     * Posts a task to be executed when the activity state has not yet been saved
     * @param task The task to be executed
     * @return true if the task executed immediately, false if it has been queued
     */
    public final boolean post(ActivityTask task)
    {
        // execute it immediately if we have not been saved
        if (!isInstanceStateSaved()) {
            task.exec(this);
            return true;
        }

        // save it for better times
        retainFragment.tasks.add(task);
        return false;
    }

    /** Fragment used to retain activity data among re-instantiations */
    public static class ActivityRetainFragment extends Fragment {

        /**
         * Returns the single instance of this fragment, creating it if necessary
         * @param activity The Activity performing the request
         * @param name The name to be given to the Fragment
         * @return The Fragment
         */
        public static ActivityRetainFragment get(XAppCompatActivity activity, String name) {

            // find the retained fragment on activity restarts
            FragmentManager fm = activity.getSupportFragmentManager();
            ActivityRetainFragment fragment = (ActivityRetainFragment) fm.findFragmentByTag(name);

            // create the fragment and data the first time
            if (fragment == null) {
                // add the fragment
                fragment = new ActivityRetainFragment();
                fm.beginTransaction().add(fragment, name).commit();
            }

            return fragment;
        }

        /** The queued tasks */
        private LinkedList<ActivityTask> tasks = new LinkedList<>();

        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);

            // retain this fragment
            setRetainInstance(true);
        }

    }

    /** A task which needs to be performed by the activity when it is "fully operational" */
    public interface ActivityTask {

        /**
         * Executed this task on the specified activity
         * @param activity The activity
         */
        void exec(XAppCompatActivity activity);
    }
}

Dann benutze eine Klasse wie diese:

/** AppCompatDialogFragment implementing additional compatibility checks */
public abstract class XAppCompatDialogFragment extends AppCompatDialogFragment {

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag) {
        return showRequest(activity, tag, null);
    }

    /**
     * Shows this dialog as soon as possible
     * @param activity The activity to which this dialog belongs to
     * @param tag The dialog fragment tag
     * @param args The dialog arguments
     * @return true if the dialog has been shown immediately, false if the activity state has been saved
     *         and it is not possible to show it immediately
     */
    public boolean showRequest(XAppCompatActivity activity, final String tag, final Bundle args)
    {
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (args!= null) setArguments(args);
                show(activity.getSupportFragmentManager(), tag);
            }
        });
    }

    /**
     * Dismiss this dialog as soon as possible
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest()
    {
        return dismissRequest(null);
    }

    /**
     * Dismiss this dialog as soon as possible
     * @param runnable Actions to be performed before dialog dismissal
     * @return true if the dialog has been dismissed immediately, false if the activity state has been saved
     *         and it is not possible to dismissed it immediately
     */
    public boolean dismissRequest(final Runnable runnable)
    {
        // workaround as in rare cases the activity could be null
        XAppCompatActivity activity = (XAppCompatActivity)getActivity();
        if (activity == null) return false;

        // post the dialog dismissal
        return activity.post(new XAppCompatActivity.ActivityTask() {
            @Override
            public void exec(XAppCompatActivity activity) {
                if (runnable != null) runnable.run();
                dismiss();
            }
        });
    }
}

Sie können Dialoge sicher anzeigen, ohne sich über den App-Status Gedanken machen zu müssen:

public class TestDialog extends XAppCompatDialogFragment {

    private final static String TEST_DIALOG = "TEST_DIALOG";

    public static void show(XAppCompatActivity activity) {
        new TestDialog().showRequest(activity, TEST_DIALOG);
    }

    public TestDialog() {}

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        return new AlertDialog.Builder(getActivity(), R.style.DialogFragmentTheme /* or null as you prefer */)
                .setTitle(R.string.title)
                // set all the other parameters you need, e.g. Message, Icon, etc.
                ).create();
    }
}

und rufen TestDialog.show(this)Sie dann aus Ihrem XAppCompatActivity.

Wenn Sie eine allgemeinere Dialogklasse mit Parametern erstellen möchten, können Sie diese in a Bundlemit den Argumenten in der show()Methode speichern und mit getArguments()in abrufen onCreateDialog().

Der gesamte Ansatz mag etwas komplex erscheinen, aber sobald Sie die beiden Basisklassen für Aktivitäten und Dialoge erstellt haben, ist er recht einfach zu verwenden und funktioniert einwandfrei. Es kann für andere Fragmentbasierte Vorgänge verwendet werden, die von demselben Problem betroffen sein können.

gicci
quelle
0

Dieser Fehler scheint aufzutreten, weil Eingabeereignisse (z. B. Key-Down- oder On-Click-Ereignisse) nach dem onSaveInstanceStateAufruf übermittelt werden .

Die Lösung besteht darin, onSaveInstanceStateIhre Aktivität zu überschreiben und ausstehende Ereignisse abzubrechen.

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