Bij het weergeven van het dialoogvenster krijg ik “Kan deze actie niet uitvoeren na onSaveInstanceState”

Sommige gebruikers melden dat als ze de snelle actie in de meldingsbalk gebruiken, ze dichtbij komen.

Ik laat een snelle actie zien in de melding die de klasse “TestDialog”aanroept.
In de TestDialog-klasse nadat ik op de knop “snooze” heb gedrukt, zal ik de SnoozeDialog laten zien.

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

De fout is *IllegalStateException: Can not perform this action after onSaveInstanceState*.

De coderegel waar de IllegarStateException wordt geactiveerd is:

snoozeDialog.show(fm, "snooze_dialog");

De klasse breidt “FragmentActivity” uit en de klasse “SnoozeDialog” breidt “DialogFragment” uit.

Hier is het volledige stacktracé van de fout:

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)

Ik kan deze fout niet reproduceren, maar ik krijg veel foutmeldingen.

Kan iemand mij helpen hoe ik deze fout kan oplossen?


Antwoord 1, autoriteit 100%

Dit is een veelvoorkomend probleem.
We hebben dit probleem opgelost door show() te negeren en de uitzondering in de uitgebreide klasse DialogFragment af te handelen

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

Merk op dat het toepassen van deze methode de interne velden van de DialogFragment.class niet verandert:

boolean mDismissed;
boolean mShownByMe;

Dit kan in sommige gevallen tot onverwachte resultaten leiden. Gebruik commitAllowingStateLoss() beter in plaats van commit()


Antwoord 2, autoriteit 40%

Dat betekent dat je commit()(show()in het geval van DialogFragment) fragment na onSaveInstanceState().

Android slaat je fragmentstatus op in onSaveInstanceState(). Dus, als je commit()fragment na onSaveInstanceState()fragment staat zal verloren gaan.

Als gevolg hiervan, als activiteit wordt afgebroken en later opnieuw wordt gemaakt, wordt het fragment niet toegevoegd aan activiteit, wat een slechte gebruikerservaring is. Daarom staat Android ten koste van alles staatsverlies toe.

De gemakkelijke oplossing is om te controleren of de staat al is opgeslagen.

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

Opmerking: onResumeFragments() roept wanneer fragmenten worden hervat.


Antwoord 3, autoriteit 32%

Het gebruik van de nieuwe levenscyclusbereiken van Activity-KTX is zo eenvoudig als het volgende codevoorbeeld:

lifecycleScope.launchWhenResumed {
   showErrorDialog(...)
}

Deze methode kan direct worden aangeroepen na onStop() en zal het dialoogvenster met succes tonen zodra onResume() is aangeroepen bij terugkeer.


Antwoord 4, autoriteit 29%

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


Antwoord 5, autoriteit 22%

Na een paar dagen wil ik mijn oplossing delen hoe ik het heb opgelost. Om DialogFragment te laten zien, moet je de methode show()ervan negeren en commitAllowingStateLoss()aanroepen op Transactionobject. Hier is een voorbeeld in Kotlin:

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

Antwoord 6, autoriteit 16%

Als het dialoogvenster niet echt belangrijk is (het is oké om het niet te tonen wanneer de app is gesloten/niet meer in beeld is), gebruik dan:

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

En open uw dialoog(fragment) alleen als we actief zijn:

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

BEWERKEN, WAARSCHIJNLIJK BETERE OPLOSSING:

Waar onSaveInstanceState wordt aangeroepen in de levenscyclus is onvoorspelbaar, ik denk dat een betere oplossing is om isSavedInstanceStateDone() als volgt te controleren:

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

Antwoord 7, autoriteit 15%

Ik loop al jaren tegen dit probleem aan.
De internetten zijn bezaaid met tientallen (honderden? duizenden?) discussies hierover, en verwarring en desinformatie lijkt er in overvloed.
Om de situatie nog erger te maken, en in de geest van de xkcd “14 standards”-strip, gooi ik mijn antwoord in de ring.

De cancelPendingInputEvents(), commitAllowingStateLoss(), catch (IllegalStateException e)en soortgelijke oplossingen lijken allemaal afschuwelijk.

Hopelijk laat het volgende zien hoe je het probleem kunt reproduceren en oplossen:

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

Antwoord 8, autoriteit 8%

probeer alstublieft FragmentTransaction te gebruiken in plaats van FragmentManager. Ik denk dat de onderstaande code je probleem zal oplossen. Zo niet, laat het me weten.

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

BEWERKEN:

Fragmenttransactie

Controleer deze link. Ik denk dat het je vragen zal oplossen.


Antwoord 9, autoriteit 7%

Maak uw dialoogfragment-object globaal en roep diskAllowingStateLoss() aan in de methode onPause()

@Override
protected void onPause() {
    super.onPause();
    if (dialogFragment != null) {
        dialogFragment.dismissAllowingStateLoss();
    }
}

Vergeet niet om waarde toe te kennen in fragment en bel show() aan als u op de knop klikt of waar dan ook.


Antwoord 10, autoriteit 4%

Veel weergaven plaatsen gebeurtenissen op hoog niveau, zoals klikhandlers, in de gebeurteniswachtrij om uitgesteld te worden uitgevoerd. Het probleem is dus dat “onSaveInstanceState” al is aangeroepen voor de activiteit, maar dat de gebeurteniswachtrij een uitgestelde “klikgebeurtenis” bevat. Vandaar dat wanneer deze gebeurtenis naar uw handler wordt verzonden

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

en uw code showdat de IllegalStateException wordt gegenereerd.

De eenvoudigste oplossing is om de wachtrij voor gebeurtenissen op te schonen, 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();
        }
}

Antwoord 11, autoriteit 4%

gebruik deze code

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

in plaats van

yourFragment.show(fm, "fragment_tag");

Antwoord 12, autoriteit 3%

Ik heb een elegante oplossing voor dit probleem gevonden door reflectie te gebruiken.
Het probleem van alle bovenstaande oplossingen is dat de velden mDismisseden mShownByMehun status niet veranderen.

Overschrijf gewoon de methode “show” in uw eigen aangepaste onderbladdialoogfragment zoals onderstaand voorbeeld (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()
    }

Antwoord 13, autoriteit 3%

Als u de show()-functie overschrijft, DOE DIT NIET:

override fun show(manager: FragmentManager, tag: String?) {
    // mDismissed = false; is removed -> lead to wrong state
    // mShownByMe = true; is removed -> lead to wrong state
    val ft = manager.beginTransaction()
    ft.add(this, tag)
    ft.commitAllowingStateLoss()
}

Het kan leiden tot een verkeerde dialoogstatus

Doe gewoon:

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

Antwoord 14

Hoewel het nergens officieel wordt genoemd, heb ik dit probleem een ​​paar keer ondervonden. In mijn ervaring is er iets mis in de compatibiliteitsbibliotheek die fragmenten op oudere platforms ondersteunt, wat dit probleem veroorzaakt. U kunt dit testen met behulp van de normale fragmentmanager-API. Als niets werkt, kun je de normale dialoog gebruiken in plaats van het dialoogfragment.


Antwoord 15

  1. Voeg deze klasse toe aan uw project: (moet in het pakket android.support.v4.appzitten)
pakket android.support.v4.app;
/**
 * Gemaakt door Gil op 16-8-2017.
 */
openbare klasse StatelessDialogFragment breidt DialogFragment uit {
  /**
   * Geef het dialoogvenster weer, voeg het fragment toe met behulp van een bestaande transactie en voer vervolgens de
   * transactie terwijl staatsverlies wordt toegestaan.
* * Ik raad je aan om {@link #show(FragmentTransaction, String)} meestal te gebruiken, maar * dit is voor dialogen waar je echt niet om geeft. (Debug/Tracking/Advertenties etc.) * * @param transactie * Een bestaande transactie om het fragment toe te voegen. * @param-tag * De tag voor dit fragment, zoals per * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. * @return Retourneert de identifier van de vastgelegde transactie, zoals per * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. * @see StatelessDialogFragment#showAllowingStateLoss(FragmentManager, String) */ public int showAllowingStateLoss(FragmentTransaction-transactie, String-tag) { mAfgewezen = onwaar; mShownByMe = waar; transactie.add (dit, tag); mViewDestroyed = onwaar; mBackStackId = transactie.commitAllowingStateLoss(); retourneer mBackStackId; } /** * Geef het dialoogvenster weer en voeg het fragment toe aan de gegeven FragmentManager. Dit is een gemak * om expliciet een transactie aan te maken, het fragment eraan toe te voegen met de gegeven tag, en * het plegen zonder zich om de staat te bekommeren. Dit voegt nietde transactie toe aan de * achterste stapel. Wanneer het fragment wordt verwijderd, wordt een nieuwe transactie uitgevoerd om het te verwijderen * van de activiteit.
* * Ik raad je aan om {@link #show(FragmentManager, String)} meestal te gebruiken, maar dit is * voor dialogen waar je echt niet om geeft. (Foutopsporing/Tracking/Advertenties enz.) * * * @parammanager * De FragmentManager waaraan dit fragment wordt toegevoegd. * @param-tag * De tag voor dit fragment, zoals per * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. * @see StatelessDialogFragment#showAllowingStateLoss(FragmentTransaction, String) */ public void showAllowingStateLoss (FragmentManager-manager, String-tag) { mAfgewezen = onwaar; mShownByMe = waar; FragmentTransaction ft = manager.beginTransaction(); ft.add (dit, tag); ft.commitAllowingStateLoss(); } }
  1. Verleng StatelessDialogFragmentin plaats van DialogFragment
  2. Gebruik de methode showAllowingStateLossin plaats van show

  3. Geniet 😉


Antwoord 16

De volgende implementatie kan worden gebruikt om het probleem op te lossen van het veilig uitvoeren van statuswijzigingen tijdens de levenscyclus van Activity, met name voor het weergeven van dialoogvensters: als de instantiestatus al is opgeslagen (bijvoorbeeld vanwege een configuratie wijzigen), worden ze uitgesteld totdat de hervatte status is uitgevoerd.

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

Gebruik dan een klasse als deze:

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

Je kunt veilig dialogen weergeven zonder je zorgen te maken over de app-status:

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

en bel vervolgens TestDialog.show(this)vanuit uw XAppCompatActivity.

Als u een meer generieke dialoogklasse met parameters wilt maken, kunt u deze opslaan in een Bundlemet de argumenten in de show()-methode en ze ophalen met getArguments()in onCreateDialog().

De hele aanpak lijkt misschien een beetje ingewikkeld, maar als je eenmaal de twee basisklassen voor activiteiten en dialogen hebt gemaakt, is het vrij eenvoudig te gebruiken en werkt het perfect. Het kan worden gebruikt voor andere op Fragmentgebaseerde bewerkingen die door hetzelfde probleem kunnen worden getroffen.


Antwoord 17

Deze fout lijkt op te treden omdat invoergebeurtenissen (zoals key down of onclick-gebeurtenissen) worden afgeleverd nadat onSaveInstanceStateis aangeroepen.

De oplossing is om onSaveInstanceStatein uw activiteit te overschrijven en eventuele lopende evenementen te annuleren.

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

Other episodes