370 votes

obtention de l'exception "IllegalStateException : Impossible d'effectuer cette action après onSaveInstanceState".

J'ai une application Android en direct, et depuis le marché, j'ai reçu la trace de pile suivante et je n'ai aucune idée de la raison pour laquelle cela se produit, car cela ne se produit pas dans le code de l'application, mais est causé par l'un ou l'autre événement de l'application (hypothèse).

Je n'utilise pas de fragments, mais il y a quand même une référence à FragmentManager. Si quelqu'un peut m'éclairer sur certains faits cachés pour éviter ce type de problème :

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)

0 votes

Avez-vous trouvé une solution ? J'ai le même problème ici : stackoverflow.com/questions/7575921/

1 votes

2 votes

@phlebas Non, vous ne l'avez pas fait. Le vôtre concerne les dialogues, ce qui n'est pas le cas de celui-ci. La ligne supérieure de votre trace de pile qui correspond n'est pas suffisante. Le reste est très différent. Je dis cela parce que je viens de regarder votre problème et cela ne m'aide pas, malheureusement.

469voto

Ovidiu Latcu Points 20783

C'est le bug le plus stupide que j'ai rencontré jusqu'à présent. J'avais un Fragment L'application fonctionne parfaitement pour API < 11 y Force Closing sur API > 11 .

Je n'ai vraiment pas pu comprendre ce qu'ils ont changé à l'intérieur du Activity du cycle de vie dans l'appel à saveInstance mais voici comment j'ai résolu ce problème :

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

Je ne fais pas l'appel pour .super() et tout fonctionne bien. J'espère que cela vous fera gagner du temps.

EDITAR: après quelques recherches supplémentaires, il s'agit d'une bug dans le paquet de soutien.

Si vous avez besoin de sauvegarder l'instance, et d'ajouter quelque chose à votre outState Bundle vous pouvez utiliser les éléments suivants :

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

EDIT2 : cela peut également se produire si vous essayez d'effectuer une transaction après que votre Activity a disparu en arrière-plan. Pour éviter cela, vous devez utiliser commitAllowingStateLoss()

EDIT3 : Les solutions ci-dessus corrigeaient des problèmes dans les premières bibliothèques support.v4 d'après ce dont je me souviens. Mais si vous avez encore des problèmes MUST lire aussi @AlexLockwood Son blog : Fragmentation des transactions et perte d'état d'activité

Résumé de l'article du blog (mais je vous recommande vivement de le lire) :

  • JAMAIS commit() les transactions après onPause() sur le pré-Honeycomb, et onStop() sur l'après-Honeycomb
  • Soyez prudent lorsque vous effectuez des transactions à l'intérieur Activity les méthodes du cycle de vie. Utilisez onCreate() , onResumeFragments() y onPostResume()
  • Éviter d'effectuer des transactions à l'intérieur des méthodes de rappel asynchrones
  • Utilisez commitAllowingStateLoss() uniquement en dernier recours

0 votes

Cela a réglé le problème pour moi. J'utilisais la barre d'action pour contrôler la navigation des fragments et j'ai eu ce problème en essayant de recharger l'application. L'ajout d'un junk outstate a réglé le problème. Merci !

98 votes

Vous devriez utiliser commitAllowingStateLoss() au lieu de commit().

0 votes

Le onsavedinstancestate est déjà appelé avant l'appel de commit(), donc l'état que vous avez sauvegardé là sera sauvegardé.

76voto

gunar Points 7978

En cherchant dans le code source d'Android ce qui cause ce problème, on trouve que l'indicateur mStateSaved dans FragmentManagerImpl (instance disponible dans Activity) a la valeur true. Elle prend la valeur true lorsque la pile arrière est sauvegardée (saveAllState) lors d'un appel de la classe Activity#onSaveInstanceState . Par la suite, les appels de ActivityThread ne réinitialisent pas ce drapeau en utilisant les méthodes de réinitialisation disponibles dans la base de données de l'UE. FragmentManagerImpl#noteStateNotSaved() y dispatch() .

De la façon dont je vois les choses, il y a des corrections disponibles, en fonction de ce que votre application fait et utilise :

Bons moyens

Avant toute autre chose : je ferais de la publicité Article d'Alex Lockwood . Alors, d'après ce que j'ai fait jusqu'à présent :

  1. Pour les fragments et les activités qui n'ont pas besoin de conserver d'informations sur l'état, appeler commitAllowStateLoss . Tiré de la documentation :

    Permet d'exécuter le commit après la sauvegarde de l'état d'une activité. C'est dangereux car le commit peut être perdu si l'activité doit être restaurée plus tard à partir de son état, donc cela ne devrait être utilisé que dans les cas où il est acceptable que l'état de l'interface utilisateur change de façon inattendue pour l'utilisateur. Je suppose que cela peut être utilisé si le fragment montre des informations en lecture seule. Ou même s'ils montrent des informations modifiables, utilisez les méthodes de callbacks pour conserver les informations modifiées.

  2. Juste après le commit de la transaction (vous venez d'appeler commit() ), faire un appel à FragmentManager.executePendingTransactions() .

Voies non recommandées :

  1. Comme Ovidiu Latcu l'a mentionné ci-dessus, n'appelez pas super.onSaveInstanceState() . Mais cela signifie que vous perdrez tout l'état de votre activité en même temps que l'état des fragments.

  2. Remplacer onBackPressed et là, appelez seulement finish() . Cela ne pose aucun problème si votre application n'utilise pas l'API Fragments, comme dans le cas suivant super.onBackPressed il y a un appel à FragmentManager#popBackStackImmediate() .

  3. Si vous utilisez l'API Fragments et que l'état de votre activité est important/vital, vous pouvez essayer d'appeler en utilisant l'API Réflexion. FragmentManagerImpl#noteStateNotSaved() . Mais c'est un hack, ou on pourrait dire que c'est une solution de rechange. Je n'aime pas ça, mais dans mon cas, c'est tout à fait acceptable puisque j'ai un code provenant d'une ancienne application qui utilise du code déprécié ( TabActivity et implicitement LocalActivityManager ).

Voici le code qui utilise la réflexion :

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    invokeFragmentManagerNoteStateNotSaved();
}

@SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeFragmentManagerNoteStateNotSaved() {
    /**
     * For post-Honeycomb devices
     */
    if (Build.VERSION.SDK_INT < 11) {
        return;
    }
    try {
        Class cls = getClass();
        do {
            cls = cls.getSuperclass();
        } while (!"Activity".equals(cls.getSimpleName()));
        Field fragmentMgrField = cls.getDeclaredField("mFragments");
        fragmentMgrField.setAccessible(true);

        Object fragmentMgr = fragmentMgrField.get(this);
        cls = fragmentMgr.getClass();

        Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
        noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
        Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
    } catch (Exception ex) {
        Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
    }
}

A la vôtre !

0 votes

Cela semble se produire avec ActionBarSherlock aussi, sous Gingerbread, donc le cas de la vérification du Build Id semble discutable... :(

0 votes

De plus, il faut savoir que cela ne convient pas non plus à l'utilisation de l'ABS :)

0 votes

@t0mm13b : Le code ci-dessus correspond à mon projet car il n'utilise ni fragments, ni support.FragmentActivity. Il s'exécute sur Android.app.Activity et puisque l'incohérence qui déclenche l'exception est causée par FragmentManager (API niveau 11 et plus), c'est pourquoi la vérification ... Si vous pensez que la source du mal est la même pour vous aussi, n'hésitez pas à supprimer la vérification. L'ABS est une autre histoire car il fonctionne au-dessus du paquet de compatibilité et l'implémentation de support.FragmentActivity peut utiliser la même implémentation de FragmentManager et voilà : même problème.

38voto

FunkTheMonk Points 6639

Une telle exception se produira si vous essayez d'effectuer une transition de fragment après que l'activité de votre fragment ait été exécutée. onSaveInstanceState() est appelé.

Une raison pour laquelle cela peut arriver, c'est que si vous laissez un AsyncTask (o Thread ) en cours d'exécution lorsqu'une activité est arrêtée.

Toutes les transitions après onSaveInstanceState() est appelé pourrait potentiellement être perdu si le système récupère l'activité pour des ressources et la recrée plus tard.

2 votes

Hey Funk, j'ai une question ici, comment se fait-il que le onBackPressed peut être appelé sur cette activité ou fragment, si cette activité ou fragment a été arrêté. L'exception ci-dessus semble être générée par un événement de l'interface utilisateur (c'est-à-dire l'appui sur la touche BACK), je ne suis pas en mesure de trouver la relation entre la tâche asynchrone et la touche Back.

0 votes

Puisque vous pouvez sauvegarder les transitions de fragments dans l'état arrière, appuyer sur le bouton arrière peut provoquer l'inverse de la transition que vous avez sauvegardée (donc les anciens fragments reviennent). onSaveInstanceState est appelé avant que votre activité soit détruite pour restaurer les ressources du système, pas toujours après que onStop soit appelé. Désolé, ce n'était pas très clair dans ma réponse.

0 votes

Funk, mais je n'utilise pas de fragments dans mon application. Il pourrait s'agir des fragments utilisés dans le code natif. Je pensais auparavant que vous parliez de la même chose.

27voto

MJ.Ahmadi Points 38

Il suffit d'appeler super.onPostResume() avant de montrer votre fragment ou déplacez votre code dans la méthode onPostResume() après avoir appelé super.onPostResume(). Cela résout le problème !

6 votes

L'appel de onPostResume() garantit que onResumeFragments() est appelé et, pour moi, c'est la solution idéale.

5voto

user1358259 Points 81

J'ai résolu le problème avec onconfigurationchanged. Le truc, c'est que selon le cycle de vie des activités Android, lorsque vous appelez explicitement une intention (intention de caméra, ou toute autre intention), l'activité est mise en pause et onsavedInstance est appelé dans ce cas. Lorsque l'on fait pivoter l'appareil vers une position différente de celle pendant laquelle l'activité était active, les opérations sur les fragments telles que le commit de fragment provoquent une exception d'état illégal. Il y a beaucoup de plaintes à ce sujet. Il s'agit d'un problème de gestion du cycle de vie des activités Android et d'appels de méthode appropriés. Pour résoudre ce problème, j'ai fait ceci : 1-Override la méthode onsavedInstance de votre activité, et déterminer l'orientation actuelle de l'écran (portrait ou paysage) puis définir votre orientation de l'écran à elle avant que votre activité est mis en pause. de cette façon, l'activité vous verrouiller la rotation de l'écran pour votre activité dans le cas où il a été tourné par un autre. 2-Puis, outrepassez la méthode onresume de l'activité, et définissez votre mode d'orientation maintenant sur sensor de sorte qu'après que la méthode onsaved soit appelée, elle appellera une fois de plus onconfiguration pour traiter la rotation correctement.

Vous pouvez copier/coller ce code dans votre activité pour y faire face :

@Override
protected void onSaveInstanceState(Bundle outState) {       
    super.onSaveInstanceState(outState);

    Toast.makeText(this, "Activity OnResume(): Lock Screen Orientation ", Toast.LENGTH_LONG).show();
    int orientation =this.getDisplayOrientation();
    //Lock the screen orientation to the current display orientation : Landscape or Potrait
    this.setRequestedOrientation(orientation);
}

//A method found in stackOverflow, don't remember the author, to determine the right screen orientation independently of the phone or tablet device 
public int getDisplayOrientation() {
    Display getOrient = getWindowManager().getDefaultDisplay();

    int orientation = getOrient.getOrientation();

    // Sometimes you may get undefined orientation Value is 0
    // simple logic solves the problem compare the screen
    // X,Y Co-ordinates and determine the Orientation in such cases
    if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        Configuration config = getResources().getConfiguration();
        orientation = config.orientation;

        if (orientation == Configuration.ORIENTATION_UNDEFINED) {
        // if height and widht of screen are equal then
        // it is square orientation
            if (getOrient.getWidth() == getOrient.getHeight()) {
                orientation = Configuration.ORIENTATION_SQUARE;
            } else { //if widht is less than height than it is portrait
                if (getOrient.getWidth() < getOrient.getHeight()) {
                    orientation = Configuration.ORIENTATION_PORTRAIT;
                } else { // if it is not any of the above it will defineitly be landscape
                    orientation = Configuration.ORIENTATION_LANDSCAPE;
                }
            }
        }
    }
    return orientation; // return value 1 is portrait and 2 is Landscape Mode
}

@Override
public void onResume() {
    super.onResume();
    Toast.makeText(this, "Activity OnResume(): Unlock Screen Orientation ", Toast.LENGTH_LONG).show();
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X