Lors de l’affichage de la boîte de dialog, j’obtiens «Impossible d’effectuer cette action après onSaveInstanceState»

Certains utilisateurs rapportent, s’ils utilisent l’action rapide dans la barre de notification, ils obtiennent une fermeture forcée.

Je montre une action rapide dans la notification qui appelle la classe “TestDialog”. Dans la classe TestDialog après avoir appuyé sur le bouton “snooze”, je montrerai le SnoozeDialog.

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

L’erreur est IllegalStateException: impossible d’effectuer cette action après onSaveInstanceState .

La ligne de code où l’exception IllegarStateException est déclenchée est la suivante:

 snoozeDialog.show(fm, "snooze_dialog"); 

La classe étend “FragmentActivity” et la classe “SnoozeDialog” étend “DialogFragment”.

Voici la trace complète de la stack de l’erreur:

 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) 

Je ne peux pas reproduire cette erreur, mais je reçois beaucoup de rapports d’erreur.

Quelqu’un peut-il aider comment puis-je corriger cette erreur?

C’est un problème commun. Nous avons résolu ce problème en remplaçant show () et en gérant l’exception dans la classe étendue DialogFragment

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

Cela signifie que vous commit() fragment commit() ( show() en cas de DialogFragment) après onSaveInstanceState() .

Android va enregistrer votre état de fragment sur onSaveInstanceState() . Donc, si vous commit() fragment commit() après que l’ onSaveInstanceState() fragment onSaveInstanceState() sera perdu.

Par conséquent, si l’activité est supprimée et recréée ultérieurement, le fragment n’appenda pas à l’activité, ce qui constitue une mauvaise expérience utilisateur. C’est pourquoi Android ne permet pas la perte d’état à tout prix.

La solution simple consiste à vérifier si l’état a déjà été enregistré.

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

Remarque: onResumeFragments () appellera lorsque les fragments reprendront.

Si la boîte de dialog n’est pas vraiment importante (il est acceptable de ne pas la montrer lorsque l’application est fermée / n’est plus visible), utilisez:

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

Et ouvrez votre boîte de dialog (fragment) uniquement lorsque nous courons:

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

EDIT, PROBABLY BETTER SOLUTION:

Lorsque onSaveInstanceState est appelé dans le cycle de vie est imprévisible, je pense qu’une meilleure solution est de vérifier isSavedInstanceStateDone () comme ceci:

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

Après quelques jours, je veux partager ma solution pour la corriger, pour afficher DialogFragment, vous devez remplacer la méthode show() et appeler commitAllowingStateLoss() sur l’object Transaction . Voici l’exemple dans Kotlin:

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

Veuillez essayer d’utiliser FragmentTransaction au lieu de FragmentManager. Je pense que le code ci-dessous va résoudre votre problème. Si non, s’il vous plaît faites le moi savoir.

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

MODIFIER:

Transaction de fragment

S’il vous plaît vérifier ce lien. Je pense que cela va résoudre vos questions.

J’ai rencontré ce problème pendant des années.
Les internets sont parsemés de scores (des centaines, des milliers?) De discussions à ce sujet, et la confusion et la désinformation y sont nombreuses.
Pour aggraver la situation, et dans l’esprit de la bande dessinée «14 standards» de xkcd, je lance ma réponse sur le ring.
xkcd 14 standards

Les cancelPendingInputEvents() , commitAllowingStateLoss() , catch (IllegalStateException e) et similaires semblent toutes atroces.

Espérons que ce qui suit montre facilement comment reproduire et résoudre le problème:

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

Bien que ce ne soit pas officiellement mentionné nulle part, j’ai rencontré ce problème à plusieurs resockets. Dans mon expérience, il y a quelque chose qui ne va pas dans la bibliothèque de compatibilité prenant en charge les fragments sur les anciennes plates-formes, ce qui pose problème. Vous utilisez le test en utilisant l’API du gestionnaire de fragment normal. Si rien ne fonctionne, vous pouvez utiliser le dialog normal au lieu du fragment de dialog.

  1. Ajoutez cette classe à votre projet: (doit être dans le package android.support.v4.app )
 package android.support.v4.app;


 / **
  * Créé par Gil le 16/08/2017.
  * /

 la classe publique StatelessDialogFragment étend DialogFragment {
     / **
      * Afficher la boîte de dialog, en ajoutant le fragment en utilisant une transaction existante, puis en validant le
      * transaction tout en permettant la perte d'état. 
* * Je vous recommande d'utiliser {@link #show (FragmentTransaction, Ssortingng)} la plupart du temps, mais * Ceci est pour les dialogs dont vous ne vous souciez pas vraiment. (Débogage / Suivi / Annonces etc.) * * transaction @param * Une transaction existante dans laquelle append le fragment. * balise @param * La balise pour ce fragment, selon * {@link FragmentTransaction # add (Fragment, Ssortingng) FragmentTransaction.add}. * @return Retourne l'identifiant de la transaction validée, conformément à * {@link FragmentTransaction # commit () FragmentTransaction.commit ()}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentManager, Ssortingng) * / public int showAllowingStateLoss (transaction FragmentTransaction, balise Ssortingng) { mDismissed = false; mShownByMe = true; transaction.add (this, tag); mViewDestroyed = false; mBackStackId = transaction.commitAllowingStateLoss (); retourne mBackStackId; } / ** * Affichez la boîte de dialog en ajoutant le fragment au FragmentManager donné. Ceci est une commodité * pour créer explicitement une transaction, en y ajoutant le fragment avec la balise donnée, et * la commettre sans se soucier de l'état. Cela n'ajoute pas la transaction au * stack de retour. Lorsque le fragment est rejeté, une nouvelle transaction sera exécutée pour le supprimer. * de l'activité.
* * Je vous recommande d’utiliser la plupart du temps {@link #show (FragmentManager, Ssortingng)} mais ceci est * pour les dialogs dont vous ne vous souciez pas vraiment. (Débogage / Suivi / Annonces etc.) * * * gestionnaire de @param * Le FragmentManager auquel ce fragment sera ajouté. * balise @param * La balise pour ce fragment, selon * {@link FragmentTransaction # add (Fragment, Ssortingng) FragmentTransaction.add}. * @see StatelessDialogFragment # showAllowingStateLoss (FragmentTransaction, Ssortingng) * / public void showAllowingStateLoss (gestionnaire FragmentManager, balise Ssortingng) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction (); ft.add (this, tag); ft.commitAllowingStateLoss (); } }
  1. Étendre StatelessDialogFragment au lieu de DialogFragment
  2. Utilisez la méthode showAllowingStateLoss au lieu de show

  3. Prendre plaisir 😉

L’implémentation suivante peut être utilisée pour résoudre le problème de l’exécution des modifications d’état en toute sécurité pendant le cycle de vie de l’ Activity , en particulier pour l’affichage des dialogs: si l’état de l’instance a déjà été enregistré (par exemple suite à un changement de configuration) a été effectuée.

 public abstract class XAppCompatActivity extends AppCompatActivity { private Ssortingng 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, Ssortingng 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 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); } } 

Ensuite, en utilisant une classe comme celle-ci:

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

Vous pouvez afficher des boîtes de dialog en toute sécurité sans vous soucier de l’état de l’application:

 public class TestDialog extends XAppCompatDialogFragment { private final static Ssortingng 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.ssortingng.title) // set all the other parameters you need, eg Message, Icon, etc. ).create(); } } 

puis appelez TestDialog.show(this) depuis votre XAppCompatActivity .

Si vous voulez créer une classe de dialog plus générique avec des parameters, vous pouvez les enregistrer dans un Bundle avec les arguments de la méthode show() et les récupérer avec getArguments() dans onCreateDialog() .

L’approche globale peut sembler un peu complexe, mais une fois que vous avez créé les deux classes de base pour les activités et les dialogs, il est assez facile à utiliser et fonctionne parfaitement. Il peut être utilisé pour d’autres opérations basées sur un Fragment qui pourraient être affectées par le même problème.

De nombreuses vues publient des événements de haut niveau, tels que des gestionnaires de clics, dans la queue d’événements pour s’exécuter différés. Le problème est donc que “onSaveInstanceState” a déjà été appelé pour l’activité, mais la file d’événements contient un “événement click” différé. Par conséquent, lorsque cet événement est envoyé à votre gestionnaire

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

et votre code show l’exception IllegalStateException est levée.

La solution la plus simple consiste à nettoyer la queue des événements, dans 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(); } } 

Cette erreur semble se produire car les événements d’entrée (tels que les événements clé ou onclick) sont délivrés après l’ onSaveInstanceState de onSaveInstanceState .

La solution consiste à remplacer onSaveInstanceState dans votre activité et à annuler les événements en attente.

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