Pojawienie się błędu „Java.lang.IllegalStateException Activity zostało zniszczone” podczas używania kart z ViewPager

110

Mam aplikację, która polega na używaniu ActionBarSherlock w trybie tabulacji. Mam 5 zakładek, a zawartość każdej zakładki jest obsługiwana za pomocą fragmentów. Jednak dla tab2 mam fragment, którego plik xml zawiera element ViewPager, który z kolei ma kilka stron fragmentów. Kiedy początkowo uruchamiam aplikację, to mogę bez problemu przełączać się między zakładkami, ale po drugim naciśnięciu tab2 pojawia się wspomniany powyżej błąd. Główna działalność jest następująca:

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) 
        {

        }
    }
}

Klasa fragmentu bez ViewPagera jest następująca:

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

Klasa fragmentu z ViewPager jest następująca:

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

Z tego, co przeczytałem w różnych miejscach (i proszę mnie poprawić, jeśli się mylę), dzieje się tak, ponieważ menedżer fragmentów w drugim przebiegu próbuje ponownie wykorzystać fragmenty z działania, które już nie istnieje, dając tym samym błąd. Ale nie jestem pewien, dlaczego tak się dzieje tutaj, ponieważ nie używam aktywności fragmentów. Według logcat błąd znajduje się w klasie Tab2, w metodzie onViewCreated w wierszu mViewPager.setAdapter (mMyFragmentPagerAdapter). Każda pomoc jest bardzo ceniona ... Dzięki.

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
źródło
Więc myślę, że mogłem znaleźć problem. Patrząc na zmienną mMyFragmentPagerAdapter (klasa Tab2) przez debugger eclipse, zauważyłem, że ma ona zmienną FragmentManager, która po kliknięciu Tab2 po raz pierwszy miała pole o nazwie mActivity, które wskazywało na MainActivity. na innej karcie i patrząc ponownie na mActivity, miał on wartość zero, co być może wyjaśnia, dlaczego podając błąd Aktywność została zniszczona.
Yulric Sequeira

Odpowiedzi:

284

Wydaje się, że jest to błąd w nowo dodanej obsłudze zagnieżdżonych fragmentów. Zasadniczo dziecko FragmentManagerkończy się złamanym stanem wewnętrznym, gdy jest oderwane od aktywności. Krótkoterminowym obejściem, które rozwiązało problem, jest dodanie następujących elementów do onDetach()każdego, do Fragmentktórego dzwonisz 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
źródło
21
Jeśli spojrzysz na implementację Fragment, zobaczysz, że przechodząc do stanu odłączonego, zresetuje swój stan wewnętrzny. Jednak nie resetuje mChildFragmentManager (jest to błąd w bieżącej wersji biblioteki obsługi). Powoduje to, że nie dołącza ponownie menedżera fragmentów podrzędnych, gdy fragment jest ponownie dołączany, powodując wyjątek, który zobaczyłeś.
Marcus Forsell Stahre
14
Chyba żartujesz. Cieszę się, że to opublikowałeś, ale żałuję.
secureboot,
8
Ten błąd jest śledzony w narzędziu do śledzenia problemów Android Open Source: code.google.com/p/android/issues/detail?id=42601
Kristopher Johnson
3
Szczypta soli, ale to nie działa przy użyciu wersji support-v4 lub wersji android.app. Więc błąd występuje nie tylko w bibliotece wsparcia
gbero
6
Wydaje się, że naprawiono to w wersji 24.0.0 obsługi biblioteki. Metoda „performDetach ()” z „android.support.v4.app.Fragment”, wykonaj „mChildFragmentManager = null;”.
Emerson Dallagnol
13

Mam dokładnie ten sam problem. Jedyne obejście, jakie znalazłem, to zastąpienie fragmentów nową instancją za każdym razem, gdy zmieniane są zakładki.

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

Nie jest to prawdziwe rozwiązanie, ale nie znalazłem sposobu na ponowne użycie poprzedniej instancji fragmentu ...

jekatt
źródło
1
Wow wielkie dzięki ... to rozwiązało problem.Czy można wyjaśnić, dlaczego to działa? Łamałem głowę próbując naprawić błąd za pomocą FragmentManagera.
Yulric Sequeira
1
Czy przeanalizowałeś swój ślad pamięci? Ponieważ to obejście może go zwiększyć, przynajmniej podejrzewam
nmxprime
nie wiem, dlaczego to działa .. nie wiem, jak to działa, ale działa lol
7

Napotkałem ten sam problem podczas wywoływania super.onCreate()na końcu mojej metody. Powód: attachActivity()jest wywoływany onCreate () elementu FragmentActivity. Podczas zastępowania onCreate()i, na przykład, tworzenia kart, menedżer kart będzie próbował przełączyć się na fragment, nie mając aktywności dołączonej do FragmentManager.

Proste rozwiązanie: przenieś wywołanie do super.onCreate()nagłówka treści funkcji.

Ogólnie wydaje się, że istnieje wiele powodów, dla których ten problem może wystąpić. To tylko kolejny ...

Matthias

user1050133
źródło
Dziękuję bardzo, uratowałeś mi dzień! Miałem ten sam problem i nadal nie mogłem go rozwiązać po tygodniu; (.
Jestem
4

Chciałem dodać, że mój problem był związany z działaniem, w którym próbowałem wykonać FragmentTransactiononCreate ZANIM zadzwoniłem super.onCreate(). Właśnie przeszedłem super.onCreate()na szczyt funkcji i działało dobrze.

sgarman
źródło
2

Wystąpił ten błąd, ponieważ korzystałem z LocalBroadcastManager i tak:

unregisterReceiver(intentReloadFragmentReceiver);

zamiast:

LocalBroadcastManager.getInstance(this).unregisterReceiver(intentReloadFragmentReceiver);
Malachiasz
źródło
1
to nie rozwiązuje mojego problemu. Ale dzięki, pomogło mi to poprawić moje błędy przy używaniu BroadcastReceivers
nmxprime.
Co się stanie, jeśli kontekst Twojego działania jest pusty. Wtedy „to” nie byłoby ważne.
Igor Ganapolsky
kiedy kontekst działań może być pusty? Myślę, że nigdy.
Malachiasz
2

I napotkał ten sam problem i okazało się, że lateron, mam nieodebrane się super.onCreate( savedInstanceState );w onCreate()od FragmentActivity.

Swapnil Chaudhari
źródło
1

Ten sam błąd wystąpił podczas próby uzyskania dostępu do dziecka, FragmentManagerzanim fragment został w pełni zainicjowany (tj. Został dołączony do działania lub przynajmniej onCreateView()wywołany). W przeciwnym razie FragmentManagerzostaje zainicjowany nulldziałaniem powodującym wspomniany wyjątek.

ubuntudroid
źródło
1

Zmuszam fragment zawierający fragment podrzędny do NULL w onPause i to rozwiązuje mój problem

fragment = null;
MobileMon
źródło
1

Wiem, że to stary post, ale sugerowane odpowiedzi nie zadziałały po mojej stronie. Chcę to zostawić tutaj na wypadek, gdyby ktoś uznał to za przydatne.

Co zrobiłem to:

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

Następnie w:

@Override
public void onPause() {
    super.onPause();
    // remove all attached fragments
    for(Fragment fragment: fragmentPages){
        getChildFragmentManager().beginTransaction().remove(fragment).commit();
    }
}
Kwadratowe pudełko
źródło
0

Miałem ten problem i nie mogłem znaleźć tutaj rozwiązania, więc chcę udostępnić moje rozwiązanie na wypadek, gdyby ktoś inny miał ten problem ponownie.

Miałem taki kod:

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

i rozwiązał problem, usuwając wiersz "onDestroy ();"

public void finishAction() {
  finish();
}

Powód, dla którego napisałem początkowy kod: Wiem, że kiedy wykonujesz "finish ()", działanie wywołuje "onDestroy ()", ale używam wątków i chciałem się upewnić, że wszystkie wątki zostaną zniszczone przed rozpoczęciem następnej czynności i wygląda na to, że „finish ()” nie zawsze jest natychmiastowa. Potrzebuję przetworzyć / zredukować wiele „Bitmap” i wyświetlać duże „Bitmapy” i pracuję nad poprawieniem wykorzystania pamięci w mojej aplikacji

Teraz zabiję wątki inną metodą i wykonam tę metodę z "onDestroy ();" i kiedy myślę, że muszę zabić wszystkie wątki.

public void finishAction() {
  onDestroyThreads();
  finish();
}
user713059
źródło
0

Miałem ten problem i sobie sprawę, że to dlatego, że dzwoni setContentView(int id)dwa razy w moich Activity„sonCreate

LukasE078
źródło
0

To doprowadziło mnie do szaleństwa na punkcie platformy Xamarin.

Natknąłem się na to z implementacją ViewPager dla TabLayout WITHIN a Fragment, który jest sam zaimplementowany w DrawerLayout:

 - DrawerLayout
   - DrawerFragment
     - TabLayout
     - TabViewPager
       - TabViewPagerFragments

Więc musisz zaimplementować następujący kod w swoim DrawerFragment . Pamiętaj, aby wybrać prawidłową ścieżkę FragmentManager. Ponieważ możesz mieć dwie różne referencje FragmentManager:

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

-> Wybierz ten, którego używasz. Jeśli chcesz skorzystać z ChildFragmentManager, musisz użyć deklaracji klasy Android.App.FragmentManager dla swojego ViewPager!

Android.Support.V4.App.FragmentManager

Zaimplementuj następującą metodę w swoim „głównym” fragmencie - w tym przykładzie: 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 {}

Oznacza to, że trzeba było zainicjować ViewPager za pomocą Android.App.FragmentManager.

Zaimplementuj następującą metodę w swoim „głównym” fragmencie - w tym przykładzie: 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+"");
    }
}
Lepidopteron
źródło
0

Błąd został naprawiony w najnowszej wersji Androidax. Słynne obejście spowoduje teraz awarię. więc nie potrzebujemy tego teraz.

vipcxj
źródło