obtention de l'exception «IllegalStateException: impossible d'exécuter cette action après onSaveInstanceState»

355

J'ai une application Android en direct, et du marché, j'ai reçu la trace de pile suivante et je ne sais pas pourquoi cela se produit car cela ne se produit pas dans le code de l'application mais est provoqué par un ou l'autre événement de l'application (hypothèse)

Je n'utilise pas Fragments, il y a toujours une référence de FragmentManager. Si un organisme peut éclairer 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)  
dcool
la source
Avez-vous déjà trouvé une solution? Avoir le même problème ici: stackoverflow.com/questions/7575921/…
nhaarman
1
J'ai eu le même problème et
phlebas
2
@phlebas Non, vous ne l'avez pas fait. Le vôtre concerne les dialogues, et ce n'est pas le cas. La ligne supérieure de votre correspondance de trace de pile 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 malheureusement pas.
themightyjon
Utilisez-vous un thread ou AsynTask dans cette activité?
José Castro
21
Je discute de cette erreur dans mon article de blog ... vous devriez le lire. :)
Alex Lockwood

Réponses:

455

C'est le bug le plus stupide que j'ai rencontré jusqu'à présent. J'avais une Fragmentapplication fonctionnant parfaitement pour API <11 et Force Closingsur API> 11 .

Je ne pouvais vraiment pas comprendre ce qu'ils avaient changé dans le Activitycycle de vie lors de 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 tout simplement pas l'appel à .super()et tout fonctionne très bien. J'espère que cela vous fera gagner du temps.

EDIT: après quelques recherches supplémentaires, il s'agit d'un bug connu dans le package de support.

Si vous devez enregistrer l'instance et ajouter quelque chose à votre, outState Bundlevous pouvez utiliser ce qui suit:

@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 avoir Activitydisparu en arrière-plan. Pour éviter cela, vous devez utilisercommitAllowingStateLoss()

EDIT3: Les solutions ci-dessus corrigeaient des problèmes dans les premières bibliothèques support.v4 de ce dont je me souviens. Mais si vous avez encore des problèmes avec cela, vous DEVEZ également lire le blog de @AlexLockwood : Transactions de fragments et perte d'état d'activité

Résumé du billet de blog (mais je vous recommande fortement de le lire):

  • Jamais de commit() transactions après onPause()sur pré-Honeycomb et onStop()sur post-Honeycomb
  • Soyez prudent lorsque vous validez des transactions dans les Activityméthodes de cycle de vie. Utiliser onCreate() , onResumeFragments()etonPostResume()
  • Évitez d'effectuer des transactions dans les méthodes de rappel asynchrones
  • Utiliser commitAllowingStateLoss()uniquement en dernier recours
Ovidiu Latcu
la source
97
Vous devez utiliser commitAllowingStateLoss () au lieu de commit ()
meh
7
Donc, ne pas appeler super dans onSaveInstanceState empêchera le FragmentManager de pouvoir sauvegarder l'état de tous les fragments et les restaurer. Cela pourrait entraîner des problèmes de rotation. De plus, je viens d'essayer l'autre chose à propos de la mise de courrier indésirable dans le bundle et cela ne fait aucune différence pour moi. Je ne sais pas comment cela se passerait - le bogue que vous avez référencé dans le package de support est une NullPointerException, et ne ressemble pas beaucoup à cette IllegalStateException ...
themightyjon
56
@meh commitAllowingStateLoss()n'évite que l'exception. Il ne protège pas votre application d'une perte d'état accidentelle. Voir cet article de blog .
Alex Lockwood
2
@AlexLockwood donc à partir de ce billet de blog, nous pouvons apprendre que nous devons faire tous nos appels réseau à l'intérieur du fragment (et afficher une progression temporaire ui si nécessaire) et c'est la seule façon d'éviter d'éviter cette exception lorsqu'elle se produit probablement parce que commit est appelé après un appel de méthode asynchrone.
meh
1
Je n'obtiens pas le premier point: "NE JAMAIS valider () de transactions après ... onStop () sur post-Honeycomb". Que faire si j'ai besoin d'un bouton pour déclencher le remplacement d'un fragment par un autre? Dois-je mettre un booléen qui vérifie si l'activité est terminée sur Stop, et si c'est le cas, appeler commitAllowingStateLoss à la place? Aussi, que se passe-t-il si j'ai un fragment dans un fragment que je dois remplacer en cliquant sur un bouton?
développeur Android
76

La recherche dans le code source Android sur les causes de ce problème donne à l'indicateur mStateSaved dans la FragmentManagerImplclasse (instance disponible dans Activity) la valeur true. Il est défini sur true lorsque la pile arrière est enregistrée (saveAllState) à l'appel de Activity#onSaveInstanceState. Ensuite, les appels d'ActivityThread ne réinitialisent pas cet indicateur en utilisant les méthodes de réinitialisation disponibles à partir de FragmentManagerImpl#noteStateNotSaved()et dispatch().

Selon moi, il existe des correctifs disponibles, selon ce que fait et utilise votre application:

De bonnes manières

Avant toute chose: j'annoncerais un article d'Alex Lockwood . Ensuite, d'après ce que j'ai fait jusqu'à présent:

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

    Permet l'exécution du commit après l'enregistrement de l'état d'une activité. Ceci est dangereux car la validation peut être perdue si l'activité doit être restaurée ultérieurement à 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 manière inattendue sur l'utilisateur ». Je suppose que cela peut être utilisé si le fragment affiche des informations en lecture seule. Ou même s'ils affichent des informations modifiables, utilisez les méthodes de rappel pour conserver les informations modifiées.

  2. Juste après la validation de la transaction (vous venez d'appeler commit()), appelez FragmentManager.executePendingTransactions().

Moyens non recommandés:

  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é ainsi que l'état des fragments.

  2. Remplacer onBackPressedet appeler là uniquement finish(). Cela devrait être OK si votre application n'utilise pas l'API Fragments; comme super.onBackPressedil y a un appel à FragmentManager#popBackStackImmediate().

  3. Si vous utilisez à la fois l'API Fragments et que l'état de votre activité est important / vital, vous pouvez essayer d'appeler à l'aide de l'API de réflexion FragmentManagerImpl#noteStateNotSaved(). Mais c'est un hack, ou on pourrait dire que c'est une solution de contournement. Je n'aime pas ça, mais dans mon cas, c'est tout à fait acceptable car j'ai un code d'une application héritée qui utilise du code obsolète ( TabActivityet 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);
    }
}

À votre santé!

gunar
la source
Cela semble se produire également avec ActionBarSherlock, sous Gingerbread, donc le cas de la vérification de l'ID de construction semble théorique ... :(
t0mm13b
En outre, vous devez souligner - cela ne convient pas non plus à l'utilisation de l'ABS :)
t0mm13b
@ t0mm13b: Le code ci-dessus représente mon projet car il n'utilise ni fragments, ni support.FragmentActivity. Il fonctionne sur android.app.Activity et puisque l'incohérence qui déclenche l'exception est causée par FragmentManager (API niveau 11 vers le haut), c'est pourquoi la vérification ... Si vous croyez que la source du mal est la même pour vous aussi, n'hésitez pas pour retirer le chèque. ABS est une autre histoire car il s'exécute au-dessus du package de compatibilité et de la mise en œuvre du support.FragmentActivity peut utiliser la même implémentation de FragmentManager et le tour est joué: même problème.
gunar
@ t0mm13b: pour ajouter plus car 600 caractères ne suffisent pas, vous devez d'abord rechercher ce qui vous cause vraiment cela. Vous devez également réaliser que ci-dessus est un hack laid et je ne prends aucune responsabilité s'il fonctionne ou non (pour moi, c'était la meilleure solution compte tenu des circonstances). Si vous devez l'utiliser, vérifiez à nouveau le code source de la compatibilité pour les noms de variables car ceux-ci peuvent différer du package standard. J'espère que ce problème sera résolu par les prochaines versions du package de compatibilité, mais d'après l'expérience Android, il y a peu de chances de se produire ...
gunar
Uhhh ... C'est exactement le même problème que dans ce rapport de bogue qui est le point de la question de ce PO. Je maintiens mon commentaire - vous auriez dû explicitement mettre un avis de non-responsabilité et dire qu'il n'est pas garanti et auriez également dû dire que vous n'avez pas utilisé de fragments - sinon pourquoi s'embêter à poster cette réponse non plus! :) Je dis juste ...
t0mm13b
35

Une telle exception se produira si vous essayez d'effectuer une transition de fragment après l' onSaveInstanceState()appel de l' activité de votre fragment .

Une des raisons pour lesquelles cela peut se produire est que vous laissez un AsyncTask(ou Thread) courir quand une activité s'arrête.

Toute transition après l' onSaveInstanceState()appel est potentiellement perdue si le système récupère l'activité pour les ressources et la recrée plus tard.

FunkTheMonk
la source
2
Hey Funk, j'ai une question ici, comment se fait-il que l'onBackPressed puisse être appelé sur cette activité ou ce fragment, si cette activité ou ce fragment a été arrêté. L'exception ci-dessus semble être générée à partir d'un événement d'interface utilisateur (c'est-à-dire en appuyant sur la touche BACK), je ne suis pas en mesure de découvrir la relation entre la tâche asynchrone et la touche retour.
dcool
Étant donné que vous pouvez enregistrer les transitions de fragments à l'état arrière, appuyer sur le bouton de retour peut provoquer l'inverse de la transition que vous enregistrez (de sorte que les anciens fragments reviennent). onSaveInstanceState est appelé avant la destruction de votre activité pour restaurer les ressources sur le système, pas toujours après l'appel de onStop. Désolé, ce n'était pas très clair dans ma réponse.
FunkTheMonk
Funk, mais je n'utilise aucun fragment dans mon application. Il peut s'agir des fragments utilisés dans le code natif. plus tôt, je pensais que vous parliez de la même chose.
dcool
1
J'ai eu une AsyncTask avec une référence à un fragment. Problème résolu après avoir supprimé l'appel super () de onSaveInstanceState et remplacé la référence de mon AsyncTask par un <Fragment> WeakReference.
Buffalo
2
@Buffalo Ce n'est certainement pas une solution au problème. Vous devriez toujours appeler super.onSaveInstanceState().
Alex Lockwood
27

Appelez simplement super.onPostResume () avant d'afficher votre fragment ou déplacez votre code dans la méthode onPostResume () après avoir appelé super.onPostResume (). Cela résout le problème!

MJ.Ahmadi
la source
6
L'appel de onPostResume () garantit que l'onResumeFragments () est appelé et pour moi, c'est la solution idéale.
j2emanue
20

Cela peut également se produire lorsque vous appelez dismiss()un fragment de boîte de dialogue après que l'écran a été verrouillé \ vide et que l'état de l'instance de la boîte de dialogue Activity + a été enregistré. Pour contourner cet appel:

dismissAllowingStateLoss()

Littéralement chaque fois que je rejette une boîte de dialogue, je ne me soucie plus de son état de toute façon, donc c'est ok à faire - vous ne perdez en fait aucun état.

Brian Dilley
la source
2
C'était mon problème exact! Vous êtes monsieur brillant!
Tash Pemhiwa
17

Solution courte et fonctionnelle:

Suivez les étapes simples:

Étape 1 : remplacer l'état onSaveInstanceState dans le fragment respectif. Et supprimez la super méthode.

@Override
public void onSaveInstanceState(Bundle outState) {
};

Étape 2 : utilisez CommitAllowingStateLoss (); au lieu de commit (); tandis que les opérations de fragment.

fragmentTransaction.commitAllowingStateLoss();
Basbous
la source
1
La suppression de la super méthode a fait l'affaire, pouvez-vous expliquer pourquoi cependant? Est-il sûr de supprimer cela?
Bruce
7
il n'est pas sûr de supprimer super (), vous perdrez d'autres données après cela!
deadfish
7

cela a fonctionné pour moi ... j'ai découvert cela par moi-même ... j'espère que cela vous aide!

1) NE PAS avoir un FragmentManager / FragmentTransaction "statique" global.

2) onCreate, TOUJOURS initialiser à nouveau le FragmentManager!

échantillon ci-dessous: -

public abstract class FragmentController extends AnotherActivity{
protected FragmentManager fragmentManager;
protected FragmentTransaction fragmentTransaction;
protected Bundle mSavedInstanceState;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mSavedInstanceState = savedInstanceState;
    setDefaultFragments();
}

protected void setDefaultFragments() {
    fragmentManager = getSupportFragmentManager();
    //check if on orientation change.. do not re-add fragments!
    if(mSavedInstanceState == null) {
        //instantiate the fragment manager

        fragmentTransaction = fragmentManager.beginTransaction();

        //the navigation fragments
        NavigationFragment navFrag = new NavigationFragment();
        ToolbarFragment toolFrag = new ToolbarFragment();

        fragmentTransaction.add(R.id.NavLayout, navFrag, "NavFrag");
        fragmentTransaction.add(R.id.ToolbarLayout, toolFrag, "ToolFrag");
        fragmentTransaction.commitAllowingStateLoss();

        //add own fragment to the nav (abstract method)
        setOwnFragment();
    }
}
kurayami88
la source
6

J'obtenais toujours cela lorsque j'essayais de montrer un fragment dans la méthode onActivityForResult (), donc le problème était le suivant:

  1. Mon activité est suspendue et arrêtée, ce qui signifie que onSaveInstanceState () a déjà été appelé (pour les appareils pré-Honeycomb et post-Honeycomb).
  2. En cas de résultat, j'ai fait une transaction pour afficher / masquer le fragment, ce qui provoque cette IllegalStateException.

Ce que j'ai fait est le suivant:

  1. Valeur ajoutée pour déterminer si l'action que je souhaite a été effectuée (par exemple, prendre une photo de camere - isPhotoTaken) - il peut s'agir d'une valeur booléenne ou entière selon le nombre de transactions différentes dont vous avez besoin.
  2. Dans la méthode onResumeFragments () substituée, j'ai vérifié ma valeur et après avoir effectué des transactions de fragments, j'avais besoin. Dans ce cas, commit () n'a pas été effectué après onSaveInstanceState, car l'état a été renvoyé dans la méthode onResumeFragments ().
Array
la source
5

J'ai résolu le problème avec onconfigurationchanged. L'astuce est que, selon le cycle de vie de l'activité Android, lorsque vous avez explicitement appelé une intention (intention de la caméra ou toute autre); l'activité est suspendue et onsavedInstance est appelée dans ce cas. Lors de la rotation de l'appareil dans une position différente de celle pendant laquelle l'activité était active; l'exécution d'opérations de fragment telles que la validation de fragment provoque une exception d'état illégal. Il y a beaucoup de plaintes à ce sujet. Il s'agit de la gestion du cycle de vie des activités Android et des appels de méthode appropriés. Pour le résoudre, j'ai fait ceci: 1-Remplacez la méthode onsavedInstance de votre activité et déterminez l'orientation actuelle de l'écran (portrait ou paysage), puis définissez-y l'orientation de votre écran avant de suspendre votre activité. de cette façon, l'activité vous verrouillez la rotation de l'écran pour votre activité au cas où elle aurait été tournée par une autre. 2-puis, remplacez la méthode d'activité de reprise et définissez maintenant votre mode d'orientation sur capteur afin qu'après l'appel de la méthode enregistrée, il appelle une fois de plus la configuration pour gérer correctement la rotation.

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);
} 
douggynix
la source
4

J'ai eu le même problème, obtenir IllegalStateException, mais remplacer tous mes appels à commit () par commitAllowingStateLoss () n'a pas aidé.

Le coupable était un appel à DialogFragment.show ().

Je l'entoure de

try {
    dialog.show(transaction, "blah blah");
}
catch(IllegalStateException e) {
    return;
}

et ça l'a fait. OK, je ne peux pas afficher la boîte de dialogue, mais dans ce cas, ça allait.

C'était le seul endroit dans mon application où j'ai appelé FragmentManager.beginTransaction () pour la première fois mais je n'ai jamais appelé commit () donc je ne l'ai pas trouvé quand j'ai cherché "commit ()".

Le plus drôle, c'est que l'utilisateur ne quitte jamais l'application. Au lieu de cela, le tueur était une annonce interstitielle AdMob apparaissant.

Roger CS Wernersson
la source
3
Pareil ici. J'ai résolu le remplacement de la méthode 'show (FragmentManager manager, String tag)', en remplaçant 'commit' par 'commitAllowingStateLoss'; perdre quelque chose parce que je ne peux pas définir deux attributs privés de la boîte de dialogue: mDismissed et mShownByMe. Mais cela semble fonctionner à chaque fois :)
Francesco Ditrani
J'ai fait une solution alternative à DialogFragment, qui peut éviter cette exception: github.com/AndroidDeveloperLB/DialogShard
développeur Android
4

Ma solution à ce problème était

Dans les méthodes d'ajout de fragments:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    guideMapFragment = (SupportMapFragment)a.getSupportFragmentManager().findFragmentById(R.id.guideMap);
    guideMap = guideMapFragment.getMap();
    ...
}

@Override
public void onDestroyView() {
    SherlockFragmentActivity a = getSherlockActivity();
    if (a != null && guideMapFragment != null) {
        try {
            Log.i(LOGTAG, "Removing map fragment");
            a.getSupportFragmentManager().beginTransaction().remove(guideMapFragment).commit();
            guideMapFragment = null;
        } catch(IllegalStateException e) {
            Log.i(LOGTAG, "IllegalStateException on exit");
        }
    }
    super.onDestroyView();
}

Peut être mauvais, mais je n'ai rien trouvé de mieux.

mc.dev
la source
Trues .. intercepter l'exception peut éviter le plantage de l'application, mais des problèmes de comportement tels que les fragments restent à l'écran ou ne sont pas ajoutés.
Marcos Vasconcelos
4
poursuivre le défilement. la vérité est là
anil
4

J'ai eu ce problème, mais je pense que ce problème n'est pas lié à commit et commitAllowStateLoss.

Le message de trace et d'exception de pile suivant concerne commit ().

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

Mais cette exception a été provoquée par onBackPressed ()

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(Unknown Source)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(Unknown Source)
at android.support.v4.app.FragmentActivity.onBackPressed(Unknown Source)

Ils ont tous été causés par checkStateLoss ()

private void checkStateLoss() {
    if (mStateSaved) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
    if (mNoTransactionsBecause != null) {
        throw new IllegalStateException(
                "Can not perform this action inside of " + mNoTransactionsBecause);
    }

mStateSaved sera vrai après onSaveInstanceState.

Ce problème se produit rarement, je n'ai jamais rencontré ce problème, je ne peux pas le reproduire.

J'ai trouvé le problème 25517

Cela pourrait se produire dans les circonstances suivantes

  1. La touche Retour est appelée après onSaveInstanceState, mais avant le démarrage de la nouvelle activité.

  2. utiliser onStop () dans le code

Je ne sais pas quelle est la racine du problème. J'ai donc utilisé une façon laide.

@Override
public void onBackPressed() {

    try{
        super.onBackPressed();
    }catch (IllegalStateException e){
        // can output some information here
        finish();
    }
}
oO_ox
la source
Je n'ai pas vraiment résolu le problème, mais ce problème n'est pas lié à commit et commitAllowStateLoss.
oO_ox
4

J'ai le même problème dans mon application. J'ai été résolu ce problème en appelant simplement la super.onBackPressed();classe précédente et en appelant la commitAllowingStateLoss()classe actuelle avec ce fragment.

Peter
la source
2
Je vous remercie. Cette solution a résolu le problème uisng commitAllowingStateLoss()au lieu decommit()
Chintak Patel
Évitez d'utiliser commitAllowingStateLoss () medium.com/@elye.project/…
swooby
3

onSaveInstance sera appelé si un utilisateur fait pivoter l'écran afin de pouvoir charger les ressources associées à la nouvelle orientation.

Il est possible que cet utilisateur ait fait pivoter l'écran, puis en appuyant sur le bouton de retour (car il est également possible que cet utilisateur ait échappé son téléphone en utilisant votre application)

dols
la source
2
Bien que des changements de configuration (tels que des changements d'orientation) puissent entraîner cette exception, ils ne sont pas la cause première.
Alex Lockwood
2

Même problème de ma part et après une journée d'analyse de tous les articles, blog et stackoverflow, j'ai trouvé une solution simple. N'utilisez pas du tout saveInstanceState, c'est la condition avec une ligne de code. Sur le code de fragment:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(null);
    .....
Tobia Caneschi
la source
2

Cela se produit chaque fois que vous essayez de charger un fragment mais que l'activité a changé son état en onPause (). Cela se produit par exemple lorsque vous essayez de récupérer des données et de les charger dans l'activité mais au moment où l'utilisateur a cliqué sur un bouton et a déplacé à l'activité suivante.

Vous pouvez résoudre ce problème de deux manières

Vous pouvez utiliser transaction.commitAllowingStateLoss () au lieu de transaction.commit () pour charger le fragment, mais vous risquez de perdre l'opération de validation effectuée.

ou

Assurez-vous que l'activité est en cours de reprise et ne va pas se mettre en pause lors du chargement d'un fragment. Créez un booléen et vérifiez si l'activité ne va pas à l'état onPause ().

@Override
public void onResume() {
    super.onResume();
    mIsResumed = true;
}

@Override
public void onPause() {
    mIsResumed = false;
    super.onPause();
}

puis pendant le chargement du fragment, vérifiez si l'activité est présente et chargez uniquement lorsque l'activité est au premier plan.

if(mIsResumed){
 //load the fragment
}
Sharath kumar
la source
1

Merci @gunar, mais je pense qu'il y a une meilleure façon.

Selon le doc:

 * If you are committing a single transaction that does not modify the
 * fragment back stack, strongly consider using
 * {@link FragmentTransaction#commitNow()} instead. This can help avoid
 * unwanted side effects when other code in your app has pending committed
 * transactions that expect different timing.
 *
 * @return Returns true if there were any pending transactions to be
 * executed.
 */
public abstract boolean executePendingTransactions();

Utilisez donc commitNowpour remplacer:

fragmentTransaction.commit();
FragmentManager.executePendingTransactions()
JianxinLi
la source
0

Eh bien, après avoir essayé toutes les solutions ci-dessus sans succès (car, fondamentalement, je n'ai pas de transactions).

Dans mon cas, j'utilisais AlertDialogs et ProgressDialog comme fragments qui, parfois, en rotation, lors de la demande de FragmentManager, l'erreur augmente.

J'ai trouvé une solution de contournement mélangeant de nombreux messages similaires:

C'est une solution en 3 étapes, tout est fait sur votre FragmentActivity (dans ce cas, son appelé GenericActivity):

private static WeakReference<GenericActivity> activity = null; //To avoid bug for fragments: Step 1 of 3

@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    //To avoid bug for fragments: Step 2 of 3
    activity = new WeakReference<GenericActivity>(this);
}

@Override
public FragmentManager getSupportFragmentManager(){
    //To avoid bug for fragments: Step 3 of 3
    if (this == activity.get()) {
        return super.getSupportFragmentManager();
    }
    return activity.get().getSupportFragmentManager();
}
Antilope
la source
0

Quand j'utilise startactivity dans un fragment, j'obtiendrai cette exception;

Lorsque je change pour utiliser startactivityforresult, l'exception a disparu :)

Donc, le moyen le plus simple de le résoudre est d'utiliser l'api startActivityForResult :)

Question
la source
0

J'obtenais cette exception lorsque j'appuyais sur le bouton de retour pour annuler le sélecteur d'intention sur l'activité de mon fragment de carte. J'ai résolu ce problème en remplaçant le code de onResume () (où j'initialisais le fragment et validais la transaction) par onStart () et l'application fonctionne bien maintenant. J'espère que cela aide.

DCS
la source
0

Ceci est corrigé dans Android 4.2 et également dans la source de la bibliothèque de support. [*]

Pour plus de détails sur la cause (et les solutions), reportez-vous au rapport de bogue Google: http://code.google.com/p/android/issues/detail?id=19917

Si vous utilisez la bibliothèque de support, vous ne devriez pas avoir à vous soucier de ce bogue (pour longtemps) [*]. Cependant, si vous utilisez directement l'API (c'est-à-dire que vous n'utilisez pas le FragmentManager de la bibliothèque de support) et que vous ciblez une API sous Android 4.2, vous devrez essayer l'une des solutions.

[*] Au moment d'écrire ces lignes, le gestionnaire de SDK Android distribue toujours une ancienne version qui présente ce bogue.

Éditer Je vais ajouter quelques précisions ici parce que j'ai évidemment confondu d'une manière ou d'une autre celui qui a rejeté cette réponse.

Il existe plusieurs circonstances différentes (mais liées) qui peuvent provoquer la levée de cette exception . Ma réponse ci-dessus se réfère à l'instance spécifique discutée dans la question, c'est-à-dire un bogue dans Android qui a été corrigé par la suite. Si vous obtenez cette exception pour une autre raison, c'est parce que vous ajoutez / supprimez des fragments alors que vous ne devriez pas l'être (après que les états des fragments ont été enregistrés). Si vous êtes dans une telle situation, peut-être que " Fragments imbriqués - IllegalStateException" ne peut pas effectuer cette action après onSaveInstanceState " " peut vous être utile.

Benjamin Dobell
la source
0

Mon cas d'utilisation: j'ai utilisé l'écouteur en fragment pour notifier l'activité que quelque chose s'est passé. J'ai fait un nouveau fragment commit sur la méthode de rappel. Cela fonctionne parfaitement bien la première fois. Mais lors d'un changement d'orientation, l'activité est recréée avec l'état d'instance enregistré. Dans ce cas, le fragment n'est pas créé à nouveau implique que le fragment a l'auditeur qui est une ancienne activité détruite. De toute façon, la méthode de rappel sera déclenchée lors de l'action. Cela va à une activité détruite qui cause le problème. La solution consiste à réinitialiser l'auditeur en fragment avec l'activité en direct actuelle. Cela résout le problème.

Ganesh Kanna
la source
0

Ce que j'ai trouvé, c'est que si une autre application est de type boîte de dialogue et permet d'envoyer des touches à l'application d'arrière-plan, presque toutes les applications d'arrière-plan se bloqueront avec cette erreur. Je pense que nous devons vérifier chaque fois qu'une transaction est effectuée si l'instance a été enregistrée ou restaurée.

chris
la source
0

Dans mon cas, avec la même exception d'erreur, j'ai mis le "onBackPressed ()" dans un exécutable (vous pouvez utiliser n'importe laquelle de vos vues):

myView.post(new Runnable() {
                    @Override
                    public void run() {
                        onBackPressed()
                    }
                });

Je ne comprends pas pourquoi, mais ça marche!

Alecs
la source
La publication sur une vue exécutera le Runnable seulement après que la vue a été correctement présentée et dessinée à l'écran; cela signifie souvent que l'activité elle-même a complètement repris, donc aucun problème
Mercato
0

Vous appelez peut-être fragmentManager.popBackStackImmediate (); lorsque l'activité est suspendue. L'activité n'est pas terminée mais est suspendue et n'est pas au premier plan. Vous devez vérifier si l'activité est suspendue ou non avant popBackStackImmediate ().

Murat
la source
0

J'ai remarqué quelque chose de très intéressant. J'ai dans mon application la possibilité d'ouvrir la galerie du téléphone et l'appareil demande quelle application utiliser, là je clique sur la zone grise loin de la boîte de dialogue et je vois ce problème. J'ai remarqué comment mon activité passe de onPause, onSaveInstanceState à onResume, il n'arrive pas à visiter onCreateView. Je fais des transactions chez onResume. Donc, ce que j'ai fini par faire, c'est de mettre un indicateur en cours de négation onPause, mais d'être vrai surCreateView. si l'indicateur est vrai onResume alors faites onCommit, sinon commitAllowingStateLoss. Je pourrais continuer et perdre tellement de temps mais je voulais vérifier le cycle de vie. J'ai un appareil qui est sdkversion 23, et je n'ai pas ce problème, mais j'en ai un autre qui est 21, et là je le vois.

Juan Mendez
la source
-1

vous pouvez utiliser FragmentActivity.onStart avant popBackStackImmediate

comme ça:

public void backStackFragment() {
    this.start();
    getFragmentManager().popBackStackImmediate();
}

public void start(){
    FragmentActivity a = getActivity();
    if(a instanceof DepositPlanPadActivity){
      ((DepositPlanPadActivity)a).onStart();
    }
    if(a instanceof SmallChangePlanPad){
            ((SmallChangePlanPad)a).onStart();
        }
        if(a instanceof UserCenterActivity){
            ((UserCenterActivity)a).onStart();
        }
    }

http://jorryliu.blogspot.com/2014/09/illegalstateexception-can-not-perform.html

user3327339
la source