Получение ошибки «Действие Java.lang.IllegalStateException было уничтожено» при использовании вкладок с ViewPager

110

У меня есть приложение, которое состоит из использования ActionBarSherlock в режиме вкладок. У меня есть 5 вкладок, и содержимое каждой вкладки обрабатывается с помощью фрагментов. Однако для tab2 у меня есть фрагмент, XML-файл которого содержит элемент ViewPager, который, в свою очередь, имеет несколько страниц фрагментов. Когда я сначала запускаю приложение, я могу без проблем переключаться между вкладками, но когда я нажимаю вкладку 2 во второй раз, я получаю указанную выше ошибку. Основная деятельность заключается в следующем:

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

        }
    }
}

Класс фрагмента без ViewPager выглядит следующим образом:

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

Класс фрагмента с ViewPager выглядит следующим образом:

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

Из того, что я читал в разных местах (и, пожалуйста, поправьте меня, если я ошибаюсь), это происходит потому, что диспетчер фрагментов на втором проходе пытается повторно использовать фрагменты из активности, которой больше не существует, что приводит к ошибке. Но я не уверен, почему это происходит здесь, поскольку я не использую активность фрагментов. Согласно logcat, ошибка находится в классе Tab2, метод onViewCreated в строке, которая говорит mViewPager.setAdapter (mMyFragmentPagerAdapter). Любая помощь приветствуется ... Спасибо.

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)
Юлрик Секейра
источник
Так что я думаю, что нашел проблему. Посмотрев на переменную mMyFragmentPagerAdapter (класс Tab2) через отладчик eclipse, я увидел, что у него была переменная FragmentManager, которая при первом нажатии на Tab2 имела поле с именем mActivity, которое указывало на MainActivity. Но при переключении с tab2 на некоторые другая вкладка и снова взглянув на mActivity, она имела значение null, что, возможно, объясняет, почему она выдавала ошибку Activity была уничтожена.
Юлрик Секейра

Ответы:

284

Похоже, это ошибка недавно добавленной поддержки вложенных фрагментов. В основном, когда ребенок FragmentManagerотрывается от деятельности, его внутреннее состояние нарушается. Кратковременный обходной путь, который исправил это для меня, - добавить следующее к onDetach()каждому, к Fragmentчему вы обращаетесь 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);
    }
}
Маркус Форселл Старе
источник
21
Если вы посмотрите на реализацию фрагмента, вы увидите, что при переходе в отключенное состояние он сбрасывает свое внутреннее состояние. Однако он не сбрасывает mChildFragmentManager (это ошибка в текущей версии библиотеки поддержки). Это приводит к тому, что он не подключает повторно диспетчер дочерних фрагментов при повторном подключении фрагмента, что вызывает исключение, которое вы видели.
Маркус Форселл Старе
14
Ты, должно быть, шутишь. Рад, что вы нашли это опубликованное, но доброе дело.
secureboot 05
8
Эта ошибка отслеживается в системе отслеживания проблем с открытым исходным кодом Android: code.google.com/p/android/issues/detail?id=42601
Кристофер Джонсон,
3
Немного скептически, но это не удается при использовании версии support-v4 или версии android.app. Так что ошибка возникает не только в библиотеке поддержки
gbero
6
Кажется, исправлено в версии библиотеки поддержки 24.0.0. Метод performDetach () из android.support.v4.app.Fragment выполняет mChildFragmentManager = null;
Emerson Dallagnol,
13

У меня точно такая же проблема. Единственный обходной путь, который я нашел, - это заменять фрагменты новым экземпляром каждый раз, когда вкладки меняются.

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

Не настоящее решение, но я не нашел способа повторно использовать предыдущий экземпляр фрагмента ...

Джекатт
источник
1
Вау, большое спасибо ... это устранило проблему. Можно ли объяснить, почему это работает? Я ломал себе голову, пытаясь исправить ошибку с помощью FragmentManager.
Юлрик Секейра
1
Вы проанализировали свой объем памяти? Поскольку этот обходной путь может увеличить его, по крайней мере, я подозреваю
nmxprime
не знаю, почему это работает .. не знаю, как это работает, но это работает лол
7

Я столкнулся с той же проблемой при вызове super.onCreate()в конце моего метода. Причина: attachActivity()вызывается onCreate () FragmentActivity. При переопределении onCreate()и, например, создании вкладок, диспетчер вкладок попытается переключиться на фрагмент, не привязав активность к FragmentManager.

Простое решение: переместите вызов super.onCreate()в заголовок тела функции.

В общем, похоже, что эта проблема может возникнуть по множеству причин. Это просто еще один ...

Матиас

user1050133
источник
Большое спасибо, вы спасли мне день! У меня была такая же проблема, и я все еще не мог ее решить через неделю; (. Я очень рад, что вы написали этот ответ !!!
JonasPTFL
4

Хотел добавить, что моя проблема была FragmentTransactionв действии, где я пытался создать onCreate ДО того, как позвонил super.onCreate(). Я просто перешел super.onCreate()в верхнюю часть функции и работал нормально.

sgarman
источник
2

У меня была эта ошибка, потому что я использовал LocalBroadcastManager, и я сделал:

unregisterReceiver(intentReloadFragmentReceiver);

вместо того:

LocalBroadcastManager.getInstance(this).unregisterReceiver(intentReloadFragmentReceiver);
Malachiasz
источник
1
это не решает мою проблему. Но спасибо, это помогло мне исправить мои ошибки при использовании BroadcastReceivers
nmxprime
Что делать, если ваш контекст Activity равен нулю. Тогда «это» не будет действительным.
Игорь Ганапольский
когда контекст действий может быть нулевым? Думаю, никогда.
Malachiasz
2

Я столкнулся с теми же проблемами и Lateron узнал, что я пропустил звонок super.onCreate( savedInstanceState );в onCreate()из FragmentActivity.

Свапнил Чаудхари
источник
1

Я получил ту же ошибку при попытке доступа к дочернему элементу FragmentManagerдо того, как фрагмент был полностью инициализирован (то есть прикреплен к Activity или, по крайней мере, onCreateView()вызван). В противном случае FragmentManagerинициализируется nullActivity, вызывающее вышеупомянутое исключение.

ubuntudroid
источник
1

Я принудительно устанавливаю для фрагмента, содержащего дочерний фрагмент, значение NULL в onPause, и это решает мою проблему.

fragment = null;
MobileMon
источник
1

Я знаю, что это старый пост, но предложенные ответы с моей стороны не сработали. Я хочу оставить это здесь на всякий случай, если кому-то это пригодится.

Что я сделал:

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

Затем в:

@Override
public void onPause() {
    super.onPause();
    // remove all attached fragments
    for(Fragment fragment: fragmentPages){
        getChildFragmentManager().beginTransaction().remove(fragment).commit();
    }
}
Квадратная коробка
источник
0

У меня была эта проблема, и я не смог найти здесь решение, поэтому я хочу поделиться своим решением на случай, если у кого-то снова возникнет эта проблема.

У меня был такой код:

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

и решил проблему, удалив строку "onDestroy ();"

public void finishAction() {
  finish();
}

Причина, по которой я написал исходный код: я знаю, что когда вы выполняете "finish ()", действие вызывает "onDestroy ()", но я использую потоки, и я хотел убедиться, что все потоки будут уничтожены перед запуском следующего действия. , и похоже, что "finish ()" не всегда выполняется немедленно. Мне нужно обработать / уменьшить количество «растровых изображений» и отобразить большие «растровые изображения», и я работаю над улучшением использования памяти в моем приложении.

Теперь я убью потоки, используя другой метод, и я выполню этот метод из "onDestroy ();" и когда я думаю, мне нужно убить все потоки.

public void finishAction() {
  onDestroyThreads();
  finish();
}
пользователь713059
источник
0

Я имел этот вопрос и понял , что это было , потому что я звонил setContentView(int id)дважды в моем ActivityonCreate

LukasE078
источник
0

Этот сводил меня с ума по Xamarin.

Я столкнулся с этим с реализацией ViewPager для TabLayout ВНУТРИ фрагмента, который сам реализован в DrawerLayout:

 - DrawerLayout
   - DrawerFragment
     - TabLayout
     - TabViewPager
       - TabViewPagerFragments

Итак, вам нужно реализовать следующий код в своем DrawerFragment . Не забывайте выбирать правильный FragmentManager-Path. Потому что у вас может быть две разные ссылки FragmentManager:

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

-> Выберите тот, который вы используете. Если вы хотите использовать ChildFragmentManager, вам нужно было использовать объявление класса Android.App.FragmentManager для вашего ViewPager!

Android.Support.V4.App.FragmentManager

Реализуйте следующий метод в своем « основном » фрагменте - в этом примере: 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 {}

Это означает, что вам нужно было запустить ViewPager с Android.App.FragmentManager.

Реализуйте следующий метод в своем « основном » фрагменте - в этом примере: 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
источник
0

Ошибка исправлена ​​в последней версии androidx. И знаменитый обходной путь теперь вызовет сбой. так что нам это не нужно сейчас.

vipcxj
источник