wake-up-neo.net

Beim Anzeigen des Dialogs bekomme ich "Kann diese Aktion nach onSaveInstanceState nicht ausführen"

Einige Benutzer berichten, wenn sie die schnelle Aktion in der Benachrichtigungsleiste verwenden, werden sie in der Nähe.

Ich zeige in der Benachrichtigung eine schnelle Aktion, die die "TestDialog" class ..__ aufruft. In der TestDialog-Klasse, nachdem Sie auf die Schaltfläche "Snooze" geklickt haben, wird 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 Klasse "SnoozeDialog" erweitert "DialogFragment".

Hier ist der vollständige Stack-Trace 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, bekomme aber viele Fehlerberichte.

Kann jemand helfen, wie kann ich diesen Fehler beheben?

88
chrisonline

Dies ist üblich issue . Dieses Problem wurde gelöst, indem show () überschrieben und Ausnahmebedingungen in der erweiterten DialogFragment-Klasse behandelt werden

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 die Anwendung dieser Methode die internen Felder der DialogFragment.class nicht ändert:

boolean mDismissed;
boolean mShownByMe;

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

42
Rafael

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

Android speichert Ihren Fragmentstatus unter onSaveInstanceState(). Wenn Sie also commit() fragment after onSaveInstanceState() fragment state verwenden, gehen diese verloren.

Wenn Aktivität abgebrochen wird und später erneut erstellt wird, fügt das Fragment keine Aktivität hinzu, was eine schlechte Benutzererfahrung darstellt. Aus diesem Grund lässt Android keinen Staatsverlust unter allen Umständen zu.

Die einfache Lösung besteht darin, zu prü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 () ruft auf, wenn die Fragmente fortgesetzt werden.

21
Pongpat
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

11
huu duy

Wenn das Dialogfeld nicht wirklich wichtig ist (es ist in Ordnung, es 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ÖGLICH BESSERE LÖSUNG:

Wo onSaveInstanceState im Lebenszyklus aufgerufen wird, ist unvorhersehbar. Ich denke, eine bessere Lösung ist, isSavedInstanceStateDone () so 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;
}
9
Frank

Nach einigen Tagen möchte ich meine Lösung mitteilen, wie ich sie behoben habe. Um DialogFragment anzuzeigen, müssen Sie die show()-Methode überschreiben und commitAllowingStateLoss() für Transaction-Objekt aufrufen. 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) {

        }

    }
9

bitte versuchen Sie, FragmentTransaction anstelle von FragmentManager zu verwenden. Ich denke, der untenstehende Code wird Ihr Problem lösen. Wenn nicht, bitte lass es mich 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 Ihnen Fragen beantworten.

5
RIJO RV

Ich bin diesem Problem schon seit Jahren begegnet.
Das Internet ist mit Dutzenden (Hunderttausenden?) An Diskussionen darüber verstreut, und Verwirrung und Desinformation in ihnen scheint reichlich.
Um die Situation noch schlimmer zu machen, und im Geiste des xkcd-Comics "14 standards", füge ich meine Antwort in den Ring.
 xkcd 14 standards

Die cancelPendingInputEvents(), commitAllowingStateLoss(), catch (IllegalStateException e) und ähnliche Lösungen scheinen alle abscheulich zu sein.

Hoffentlich zeigt das folgende einfach, wie man das Problem reproduziert und behebt: 

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);
    }
};
4
swooby
  1. Fügen Sie diese Klasse Ihrem Projekt hinzu: (muss sich in Android.support.v4.app package befinden)
 Paket Android.support.v4.app; 


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

 public class StatelessDialogFragment erweitert DialogFragment {
 /**
 * Zeigen Sie den Dialog an, fügen Sie das Fragment mit einer vorhandenen Transaktion hinzu und bestätigen Sie anschließend die. * Transaktion unter Berücksichtigung des Staatsverlustes.
* * Ich würde empfehlen, dass Sie {@link #show (FragmentTransaction, String)} meistens verwenden, aber * Dies ist für Dialoge, die Sie wirklich nicht interessieren. (Debuggen/Tracking/Anzeigen 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 den Bezeichner der festgeschriebenen Transaktion gemäß .__ zurück. * {@link FragmentTransaction # commit () FragmentTransaction.commit ()} . * @sehe StatelessDialogFragment # showAllowingStateLoss (FragmentManager, String) */ public int showAllowingStateLoss (Transaktion FragmentTransaction, String-Tag) { mDississed = false; mShownByMe = true; transaction.add (dieses 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. Dies fügt not die Transaktion zur .__ hinzu. * zurück stapeln. Wenn das Fragment verworfen wird, wird eine neue Transaktion ausgeführt, um es zu entfernen * aus der Tätigkeit.
* * Ich würde empfehlen, {@link #show (FragmentManager, String)} zu verwenden. Dies ist jedoch * für Dialoge, die Sie wirklich nicht interessieren. (Debuggen/Tracking/Anzeigen etc.) * * * @param manager * Der FragmentManager dieses Fragment wird zu ..__ hinzugefügt. * @param Tag * Das Tag für dieses Fragment gemäß * {@link FragmentTransaction # add (Fragment, String) FragmentTransaction.add} . * @sehe StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, String) */ public void showAllowingStateLoss (FragmentManager-Manager, String-Tag) { mDississed = 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 ;)

1
Gil SH

Obwohl es nirgendwo offiziell erwähnt wird, stand ich dieses Problem einige Male vor. Nach meiner Erfahrung stimmt die Kompatibilitätsbibliothek nicht mit der Unterstützung von Fragmenten auf älteren Plattformen überein, was dieses Problem verursacht. Sie können dies mithilfe der normalen Fragmentmanager-API testen. Wenn nichts funktioniert, können Sie den normalen Dialog anstelle des Dialogfragments verwenden.

1
Dalvinder Singh

In vielen Ansichten werden übergeordnete Ereignisse, z. B. Click-Handler, in der Ereigniswarteschlange nachgestellt, 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 "click-Ereignis" enthält. Wenn also 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 ist show, die IllegalStateException wird ausgelöst.

Die einfachste Lösung ist das Löschen der Ereigniswarteschlange in 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();
        }
}
0
sim

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

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

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

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

0
Rohit Rajpal

Dieser Fehler scheint aufzutreten, da Eingabeereignisse (z. B. Tastendrück- oder Onclick-Ereignisse) nach dem Aufruf von onSaveInstanceState geliefert werden.

Die Lösung besteht darin, onSaveInstanceState in Ihrer Aktivität zu überschreiben und alle anstehenden 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();
        }
    }
}
0
William

benutze diesen Code

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

anstatt

yourFragment.show(fm, "fragment_tag");

Die folgende Implementierung kann verwendet werden, um das Problem der sicheren Statusänderung während des Activity-Lebenszyklus zu lösen, insbesondere zum Anzeigen von Dialogen: Wenn der Instanzstatus bereits gespeichert wurde (z. B. aufgrund einer Konfigurationsänderung), werden diese bis zum Wiederaufnahme-Status verschoben wurde durchgeführt.

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 verwenden Sie 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 sicher Dialoge anzeigen, ohne sich über den App-Status Gedanken zu machen:

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 Sie dann TestDialog.show(this) in Ihrer XAppCompatActivity auf.

Wenn Sie eine generischere Dialogklasse mit Parametern erstellen möchten, können Sie sie mit den Argumenten in der show()-Methode in einer Bundle speichern und mit getArguments() in onCreateDialog() abrufen.

Der gesamte Ansatz mag etwas komplex erscheinen, aber nachdem Sie die beiden Basisklassen für Aktivitäten und Dialoge erstellt haben, ist er sehr einfach zu bedienen und funktioniert einwandfrei. Es kann für andere Fragment-basierte Operationen verwendet werden, die von demselben Problem betroffen sein könnten.

0
gicci