Obtention de l'erreur «L'activité Java.lang.IllegalStateException a été détruite» lors de l'utilisation des onglets avec ViewPager

110

J'ai une application qui consiste à utiliser ActionBarSherlock en mode tab, j'ai 5 onglets et le contenu de chaque onglet est géré à l'aide de fragments. Pour tab2 cependant, j'ai un fragment dont le fichier xml contient un élément ViewPager qui à son tour a des pages de fragment. Lorsque je lance initialement l'application l'application, je suis capable de basculer entre les onglets sans problème mais lorsque j'appuie sur tab2 pour la deuxième fois, j'obtiens l'erreur mentionnée ci-dessus. L'activité principale est la suivante:

public class MainActivity extends SherlockFragmentActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ActionBar actionBar = getSupportActionBar();

        ActionBar.Tab tab1 = actionBar.newTab().setText("Tab1");
        ActionBar.Tab tab3 = actionBar.newTab().setText("Tab3");
        ActionBar.Tab tab2 = actionBar.newTab().setText("Tab2");
        ActionBar.Tab tab4 = actionBar.newTab().setText("Tab4");
        ActionBar.Tab tab5 = actionBar.newTab().setText("Tab5");

        Fragment fragment1 = new Tab1();
        Fragment fragment3 = new Tab3();
        Fragment fragment2 = new Tab2();
        Fragment fragment5 = new Tab5();
        Fragment fragment4 = new Tab4();

        tab1.setTabListener(new MyTabListener(fragment1));
        tab3.setTabListener(new MyTabListener(fragment3));
        tab2.setTabListener(new MyTabListener(fragment2));
        tab5.setTabListener(new MyTabListener(fragment5));
        tab4.setTabListener(new MyTabListener(fragment4));

        actionBar.addTab(tab1);
        actionBar.addTab(tab2);
        actionBar.addTab(tab3);
        actionBar.addTab(tab4);
        actionBar.addTab(tab5); 

        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    }

    class MyTabListener implements ActionBar.TabListener
    {
        Fragment fragment;

        public MyTabListener(Fragment fragment)
        {
            this.fragment = fragment;
        }

        @Override
        public void onTabSelected(com.actionbarsherlock.app.ActionBar.Tab tab,FragmentTransaction ft) 
        {
            ft.replace(R.id.fragment_container,fragment);
        }

        @Override
        public void onTabUnselected(com.actionbarsherlock.app.ActionBar.Tab tab,FragmentTransaction ft) 
        {

        }

        @Override
        public void onTabReselected(com.actionbarsherlock.app.ActionBar.Tab tab,FragmentTransaction ft) 
        {

        }
    }
}

La classe de fragment sans ViewPager est la suivante:

public class Tab1 extends Fragment 
{
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
    {
        return inflater.inflate(R.layout.activity_tab1, container, false);
    }
}

La classe de fragment avec le ViewPager est la suivante:

public class Tab2 extends Fragment 
{
    ViewPager mViewPager;
    private MyFragmentPagerAdapter mMyFragmentPagerAdapter;  
    private static int NUMBER_OF_PAGES = 5;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
    {
        View view =  inflater.inflate(R.layout.activity_tab2, container, false); 
        return view;
    }

    @Override
    public void onViewCreated(View view,Bundle savedInstanceState)
    {
        super.onViewCreated(view, savedInstanceState);
        mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
        mMyFragmentPagerAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());  
        mViewPager.setAdapter(mMyFragmentPagerAdapter);  
    }

    private static class MyFragmentPagerAdapter extends FragmentPagerAdapter 
    {    
        public MyFragmentPagerAdapter(FragmentManager fm) 
        {  
             super(fm);  
        }  

        @Override  
        public Fragment getItem(int index) 
        {  
             return PageFragment.newInstance("My Message " + index);
        }  

        @Override  
        public int getCount() 
        {  
             return NUMBER_OF_PAGES;  
        }  
   }
}

D'après ce que j'ai lu à différents endroits (et corrigez-moi si je me trompe), cela se produit parce que le gestionnaire de fragments sur la deuxième passe essaie de réutiliser les fragments de l'activité qui n'existe plus, donnant ainsi l'erreur. Mais je ne sais pas pourquoi cela se produit ici, car je n'utilise pas l'activité de fragment. Selon logcat, l'erreur se trouve dans la classe Tab2, méthode onViewCreated sur la ligne qui indique mViewPager.setAdapter (mMyFragmentPagerAdapter). Toute aide est grandement appréciée ... Merci.

03-04 12:01:05.468: E/AndroidRuntime(2474): FATAL EXCEPTION: main
03-04 12:01:05.468: E/AndroidRuntime(2474): java.lang.IllegalStateException: Activity has been destroyed
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1342)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.app.BackStackRecord.commitAllowingStateLoss(BackStackRecord.java:578)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:139)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.view.ViewPager.populate(ViewPager.java:1011)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.view.ViewPager.populate(ViewPager.java:880)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.view.ViewPager.setAdapter(ViewPager.java:433)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at com.example.tabs.Tab2.onViewCreated(Tab2.java:31)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:925)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1088)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1444)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:429)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.os.Handler.handleCallback(Handler.java:587)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.os.Handler.dispatchMessage(Handler.java:92)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.os.Looper.loop(Looper.java:123)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at android.app.ActivityThread.main(ActivityThread.java:3687)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at java.lang.reflect.Method.invokeNative(Native Method)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at java.lang.reflect.Method.invoke(Method.java:507)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
03-04 12:01:05.468: E/AndroidRuntime(2474):     at dalvik.system.NativeStart.main(Native Method)
Yulric Sequeira
la source
Je pense donc avoir trouvé le problème. En regardant la variable mMyFragmentPagerAdapter (classe Tab2) via le débogueur eclipse, j'ai vu qu'elle avait une variable FragmentManager qui, en cliquant sur Tab2 pour la première fois, avait un champ appelé mActivity qui pointait vers MainActivity.Mais en passant de tab2 à certains autre onglet et en regardant à nouveau mActivity, il avait une valeur nulle, ce qui explique peut-être pourquoi il donne l'erreur Activity a été détruit.
Yulric Sequeira

Réponses:

284

Cela semble être un bogue dans la prise en charge nouvellement ajoutée des fragments imbriqués. Fondamentalement, l'enfant FragmentManagerse retrouve avec un état interne brisé lorsqu'il est détaché de l'activité. Une solution de contournement à court terme qui a résolu le problème pour moi consiste à ajouter les éléments suivants à onDetach()tous ceux Fragmentque vous appelez getChildFragmentManager():

@Override
public void onDetach() {
    super.onDetach();

    try {
        Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager");
        childFragmentManager.setAccessible(true);
        childFragmentManager.set(this, null);

    } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}
Marcus Forsell Stahre
la source
21
Si vous regardez l'implémentation de Fragment, vous verrez que lors du passage à l'état détaché, il réinitialisera son état interne. Cependant, il ne réinitialise pas mChildFragmentManager (il s'agit d'un bogue dans la version actuelle de la bibliothèque de support). Cela l'empêche de rattacher le gestionnaire de fragments enfants lorsque le fragment est rattaché, ce qui provoque l'exception que vous avez vue.
Marcus Forsell Stahre
14
Vous vous moquez de moi. Heureux que vous ayez trouvé posté ceci, mais bon chagrin.
secureboot
8
Ce bogue est suivi dans le suivi des problèmes Android Open Source: code.google.com/p/android/issues/detail?id=42601
Kristopher Johnson
3
Une pincée de sel, mais cela échoue avec la version support-v4 ou la version android.app. Donc, le bogue ne se produit pas seulement dans la bibliothèque de support
gbero
6
Il semble corrigé dans la version 24.0.0 de la bibliothèque de support. La méthode 'performDetach ()', de 'android.support.v4.app.Fragment', faites 'mChildFragmentManager = null;'.
Emerson Dallagnol
13

J'ai exactement le même problème. La seule solution de contournement que j'ai trouvée est de remplacer les fragments par une nouvelle instance, chaque fois que les onglets sont modifiés.

ft.replace(R.id.fragment_container, Fragment.instantiate(PlayerMainActivity.this, fragment.getClass().getName()));

Ce n'est pas une vraie solution, mais je n'ai pas trouvé de moyen de réutiliser l'instance de fragment précédente ...

jekatt
la source
1
Wow merci beaucoup ... qui a résolu le problème.Est-il possible de donner une explication pourquoi cela fonctionne? Je me cassais la tête en essayant de corriger l'erreur en utilisant le FragmentManager.
Yulric Sequeira
1
Avez-vous analysé votre empreinte mémoire? Parce que cette solution de contournement peut l'augmenter, au moins je soupçonne
nmxprime
Je ne sais pas pourquoi cela fonctionne .. Je ne sais pas comment cela fonctionne mais cela fonctionne lol
7

J'ai rencontré le même problème lors de l'appel super.onCreate()à la fin de ma méthode. La raison: attachActivity()est appelée dans onCreate () de FragmentActivity. Lors du remplacement onCreate()et, par exemple, de la création d'onglets, le gestionnaire d'onglets essaiera de basculer vers un fragment sans avoir l'activité attachée au FragmentManager.

Solution simple: déplacez l'appel super.onCreate()vers la tête du corps de la fonction.

En général, il semble que ce problème puisse se produire pour de nombreuses raisons. Ceci est juste un autre ...

Matthias

user1050133
la source
Merci beaucoup, vous avez sauvé ma journée! J'ai eu le même problème et je ne pouvais toujours pas le résoudre après une semaine; (. Je suis donc très heureux que vous ayez écrit cette réponse !!!
JonasPTFL
4

Je voulais ajouter que mon problème était dans une activité où j'ai essayé de créer un FragmentTransactiononCreate AVANT d'appeler super.onCreate(). Je viens de passer super.onCreate()au sommet de la fonction et j'ai bien travaillé.

sgarman
la source
2

J'ai eu cette erreur parce que j'utilisais LocalBroadcastManager et j'ai fait:

unregisterReceiver(intentReloadFragmentReceiver);

au lieu de:

LocalBroadcastManager.getInstance(this).unregisterReceiver(intentReloadFragmentReceiver);
Malachiasz
la source
1
cela ne résout pas mon problème. Mais merci, cela m'a aidé à corriger mes erreurs lors de l'utilisation de BroadcastReceivers
nmxprime
Que faire si votre contexte d'activité est nul. Alors «ceci» ne serait pas valide.
IgorGanapolsky
quand le contexte des activités peut être nul? Je ne pense jamais.
Malachiasz
2

J'ai rencontré le même problème et lateron découvert que, je l' ai manqué appel à super.onCreate( savedInstanceState );dans onCreate()de FragmentActivity.

Swapnil Chaudhari
la source
1

J'ai eu la même erreur en essayant d'accéder à l'enfant FragmentManageravant que le fragment ne soit complètement initialisé (c'est-à-dire attaché à l'activité ou au moins onCreateView()appelé). Sinon, il FragmentManagerest initialisé avec une nullactivité provoquant l'exception susmentionnée.

ubuntudroid
la source
1

Je force le fragment contenant le fragment enfant à NULL dans onPause et cela résout mon problème

fragment = null;
MobileMon
la source
1

Je sais que c'est un ancien message, mais les réponses suggérées n'ont pas fonctionné de mon côté. Je veux laisser ceci ici juste au cas où quelqu'un le trouverait utile.

Ce que j'ai fait c'est:

@Override
public void onResume() {
    super.onResume();
    // add all fragments
    FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
    for(Fragment fragment : fragmentPages){
        String tag = fragment.getClass().getSimpleName();
        fragmentTransaction.add(R.id.contentPanel, fragment, tag);
        if(fragmentPages.indexOf(fragment) != currentPosition){
            fragmentTransaction.hide(fragment);
        } else {
            lastTag = tag;
        }
    }
    fragmentTransaction.commit();
}

Puis dans:

@Override
public void onPause() {
    super.onPause();
    // remove all attached fragments
    for(Fragment fragment: fragmentPages){
        getChildFragmentManager().beginTransaction().remove(fragment).commit();
    }
}
Boîte carrée
la source
0

J'ai eu ce problème et je n'ai pas pu trouver la solution ici, donc je veux partager ma solution au cas où quelqu'un d'autre aurait à nouveau ce problème.

J'avais ce code:

public void finishAction() {
  onDestroy();
  finish();
}

et résolu le problème en supprimant la ligne "onDestroy ();"

public void finishAction() {
  finish();
}

La raison pour laquelle j'ai écrit le code initial: je sais que lorsque vous exécutez "finish ()" l'activité appelle "onDestroy ()", mais j'utilise des threads et je voulais m'assurer que tous les threads sont détruits avant de commencer l'activité suivante , et il semble que "finish ()" n'est pas toujours immédiat. J'ai besoin de traiter / réduire beaucoup de «Bitmap» et d'afficher de gros «bitmaps» et je travaille sur l'amélioration de l'utilisation de la mémoire dans mon application

Maintenant, je vais tuer les threads en utilisant une méthode différente et je vais exécuter cette méthode à partir de "onDestroy ();" et quand je pense que j'ai besoin de tuer tous les fils.

public void finishAction() {
  onDestroyThreads();
  finish();
}
user713059
la source
0

J'ai eu ce problème et réalisé qu'il était parce que j'appelle setContentView(int id)deux fois dans ma Activity« sonCreate

LukasE078
la source
0

Celui-ci m'a rendu fou pour Xamarin.

J'ai rencontré cela avec une implémentation de ViewPager pour TabLayout WITHIN a Fragment, qui est elle-même implémentée dans DrawerLayout:

 - DrawerLayout
   - DrawerFragment
     - TabLayout
     - TabViewPager
       - TabViewPagerFragments

Vous devez donc implémenter le code suivant dans votre DrawerFragment . Soyez conscient de choisir le bon FragmentManager-Path. Parce que vous pouvez avoir deux références FragmentManager différentes:

  1. Android.Support.V4.App.FragmentManager
  2. Android.App.FragmentManager

-> Choisissez celui que vous utilisez. Si vous souhaitez utiliser le ChildFragmentManager, vous devez utiliser la déclaration de classe Android.App.FragmentManager pour votre ViewPager!

Android.Support.V4.App.FragmentManager

Implémentez la méthode suivante dans votre fragment «principal» - dans cet exemple: DrawerFragment

public override void OnDetach() {
    base.OnDetach();
    try {
        Fragment x = this;
        var classRefProp = typeof(Fragment).GetProperty("class_ref", BindingFlags.NonPublic | BindingFlags.Static);
        IntPtr classRef = (IntPtr)classRefProp.GetValue(x);
        var field = JNIEnv.GetFieldID(classRef, "mChildFragmentManager", "Landroid/support/v4/app/FragmentManagerImpl;");
        JNIEnv.SetField(base.Handle, field, IntPtr.Zero);
    }
    catch (Exception e) {
        Log.Debug("Error", e+"");
    }
}

Android.App.FragmentManager

public class TabViewPager: Android.Support.V13.App.FragmentPagerAdapter {}

Cela signifie que vous deviez lancer ViewPager avec Android.App.FragmentManager.

Implémentez la méthode suivante dans votre fragment «principal» - dans cet exemple: DrawerFragment

public override void OnDetach() {
    base.OnDetach();
    try {
        Fragment x = this;
        var classRefProp = typeof(Fragment).GetProperty("class_ref", BindingFlags.NonPublic | BindingFlags.Static);
        IntPtr classRef = (IntPtr)classRefProp.GetValue(x);
        var field = JNIEnv.GetFieldID(classRef, "mChildFragmentManager", "Landroid/app/FragmentManagerImpl;");
        JNIEnv.SetField(base.Handle, field, IntPtr.Zero);
    }
    catch (Exception e) {
        Log.Debug("Error", e+"");
    }
}
Lépidoptère
la source
0

Le bogue a été corrigé dans la dernière version d'Androidx. Et la fameuse solution de contournement provoquera un crash maintenant. donc nous n'en avons pas besoin maintenant.

vipcxj
la source