Obtendo o erro “Java.lang.IllegalStateException Activity has been destroy” ao usar guias com ViewPager

110

Eu tenho um aplicativo que consiste em usar o ActionBarSherlock no modo de guia. Eu tenho 5 guias e o conteúdo de cada guia é tratado com fragmentos. Para tab2, porém, eu tenho um fragmento cujo arquivo xml contém um elemento ViewPager que, por sua vez, tem algumas páginas de fragmento. Quando eu inicio o aplicativo pela primeira vez, eu consigo alternar entre as guias sem problemas, mas quando pressiono a guia 2 pela segunda vez, recebo o erro mencionado acima. A atividade principal é a seguinte:

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

        }
    }
}

A classe de fragmento sem o ViewPager é a seguinte:

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

A classe de fragmento com o ViewPager é a seguinte:

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

Pelo que li em diferentes lugares (e corrija-me se estiver errado), isso acontece porque o gerenciador de fragmentos na segunda passagem tenta reaproveitar os fragmentos da atividade que não existe mais dando o erro. Mas não sei por que isso acontece aqui, já que não estou usando atividade de fragmento. De acordo com o logcat, o erro está na classe Tab2, método onViewCreated na linha que diz mViewPager.setAdapter (mMyFragmentPagerAdapter). Qualquer ajuda é muito apreciada, obrigado.

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
fonte
Portanto, acho que posso ter encontrado o problema. Ao olhar para a variável mMyFragmentPagerAdapter (classe Tab2) através do depurador eclipse, vi que ela tinha uma variável FragmentManager que, ao clicar em Tab2 pela primeira vez, tinha um campo chamado mActivity que estava apontando para MainActivity. Mas ao mudar de tab2 para alguns outra aba e olhando para mActivity novamente tinha um valor nulo, o que talvez explique por que seu erro com Activity foi destruído.
Yulric Sequeira

Respostas:

284

Este parece ser um bug no suporte recém-adicionado para fragmentos aninhados. Basicamente, a criança FragmentManageracaba com um estado interno quebrado quando é desligada da atividade. Uma solução alternativa de curto prazo que corrigiu isso para mim é adicionar o seguinte a onDetach()cada um Fragmentque você chamar 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
fonte
21
Se você observar a implementação de Fragment, verá que, ao passar para o estado separado, ele redefinirá seu estado interno. No entanto, ele não redefine mChildFragmentManager (este é um bug na versão atual da biblioteca de suporte). Isso faz com que ele não reconecte o gerenciador de fragmento filho quando o Fragment for reconectado, causando a exceção que você viu.
Marcus Forsell Stahre
14
Você tem que estar brincando comigo. Que bom que você encontrou postado isso, mas que bom.
secureboot
8
Este bug está sendo rastreado no rastreador de problemas de código aberto do Android: code.google.com/p/android/issues/detail?id=42601
Kristopher Johnson
3
Uma pitada de sal, mas isso está falhando usando a versão support-v4 ou a versão android.app. Portanto, o bug não ocorre apenas na biblioteca de suporte
gbero
6
Parece corrigido na versão da biblioteca de suporte 24.0.0. O método 'performDetach ()', de 'android.support.v4.app.Fragment', faça 'mChildFragmentManager = null;'.
Emerson Dallagnol
13

Estou tendo exatamente o mesmo problema. A única solução alternativa que encontrei é substituir os fragmentos por uma nova instância, cada vez que as guias são alteradas.

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

Não é uma solução real, mas não encontrei uma maneira de reutilizar a instância do fragmento anterior ...

Jekatt
fonte
1
Nossa, muito obrigado ... isso resolveu o problema. É possível explicar porque funciona? Estava quebrando a cabeça tentando consertar o erro usando o FragmentManager.
Yulric Sequeira
1
Você analisou sua pegada de memória? Como esta solução alternativa pode aumentá-lo, pelo menos eu suspeito
nmxprime
não sei por que funciona ... não sei como funciona, mas funciona lol
7

Eu encontrei o mesmo problema ao chamar super.onCreate()no final do meu método. O motivo: attachActivity()é chamado em onCreate () de FragmentActivity. Ao substituironCreate() e, por exemplo, criar guias, o gerenciador de guias tentará alternar para um fragmento, embora não tenha a atividade anexada ao FragmentManager.

Solução simples: mova a chamada para super.onCreate() para o cabeçalho do corpo da função.

Em geral, parece que esse problema pode ocorrer por vários motivos. Este é só mais um ...

Matthias

user1050133
fonte
Muito obrigado, você salvou meu dia! Eu tive o mesmo problema e ainda não consegui resolvê-lo após uma semana; (. Estou muito feliz por você ter escrito esta resposta !!!
JonasPTFL
4

Queria acrescentar que meu problema estava em uma atividade onde tentei fazer um FragmentTransactiononCreate ANTES de ligar super.onCreate(). Acabei de passar super.onCreate()para o topo da função e funcionou bem.

sgarman
fonte
2

Tive este erro porque estava usando LocalBroadcastManager e fiz:

unregisterReceiver(intentReloadFragmentReceiver);

ao invés de:

LocalBroadcastManager.getInstance(this).unregisterReceiver(intentReloadFragmentReceiver);
Malachiasz
fonte
1
isso não resolve meu problema. Mas obrigado, isso me ajudou a corrigir meus erros ao usar BroadcastReceivers
nmxprime
E se o seu contexto de atividade for nulo. Então, 'isso' não seria válido.
IgorGanapolsky
quando o contexto de atividades pode ser nulo? Acho que nunca.
Malachiasz
2

Eu encontrei o mesmo problema e lateron descobriu que, eu perdi chamada para super.onCreate( savedInstanceState );em onCreate()de FragmentActivity.

Swapnil Chaudhari
fonte
1

Recebi o mesmo erro ao tentar acessar o filho FragmentManagerantes que o fragmento fosse totalmente inicializado (ou seja, anexado à Activity ou pelo menos onCreateView()chamado). Caso contrário, o FragmentManageré inicializado com uma nullActivity causando a exceção mencionada anteriormente.

ubuntudroid
fonte
1

Eu forço o fragmento que contém o fragmento filho a NULL em onPause e isso corrige meu problema

fragment = null;
MobileMon
fonte
1

Eu sei que esta é uma postagem antiga, mas as respostas sugeridas não funcionaram no meu final. Quero deixar isso aqui para o caso de alguém achar útil.

O que eu fiz foi:

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

Então em:

@Override
public void onPause() {
    super.onPause();
    // remove all attached fragments
    for(Fragment fragment: fragmentPages){
        getChildFragmentManager().beginTransaction().remove(fragment).commit();
    }
}
Caixa quadrada
fonte
0

Eu tive esse problema e não consegui encontrar a solução aqui, então quero compartilhar minha solução caso outra pessoa tenha o problema novamente.

Eu tinha este código:

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

e resolveu o problema excluindo a linha "onDestroy ();"

public void finishAction() {
  finish();
}

O motivo pelo qual escrevi o código inicial: sei que quando você executa "finish ()", a atividade chama "onDestroy ()", mas estou usando threads e queria garantir que todas as threads sejam destruídas antes de iniciar a próxima atividade , e parece que "terminar ()" nem sempre é imediato. Preciso processar / reduzir muito “Bitmap” e exibir grandes “bitmaps” e estou trabalhando para melhorar o uso da memória em meu aplicativo

Agora vou matar os threads usando um método diferente e vou executar esse método de "onDestroy ();" e quando acho que preciso matar todos os fios.

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

Eu tive esse problema e percebi que era porque eu estava chamando setContentView(int id)duas vezes em meus Activity'sonCreate

LukasE078
fonte
0

Este me deixou louco por Xamarin.

Corri para isso com uma implementação ViewPager para TabLayout dentro de um Fragment, que é implementado no DrawerLayout:

 - DrawerLayout
   - DrawerFragment
     - TabLayout
     - TabViewPager
       - TabViewPagerFragments

Portanto, você deve implementar o código a seguir em seu DrawerFragment . Esteja ciente de escolher o FragmentManager-Path correto. Porque você pode ter duas referências FragmentManager diferentes:

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

-> Escolha o que você usa. Se você quiser usar o ChildFragmentManager, deve usar a declaração de classe Android.App.FragmentManager para o seu ViewPager!

Android.Support.V4.App.FragmentManager

Implemente o seguinte método em seu fragmento "Principal" - neste exemplo: 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 {}

Isso significa que você teve que iniciar o ViewPager com Android.App.FragmentManager.

Implemente o seguinte método em seu fragmento "Principal" - neste exemplo: 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+"");
    }
}
Lepidóptero
fonte
0

O bug foi corrigido na versão mais recente do androidx. E a famosa solução alternativa causará um crash agora. então não precisamos disso agora.

vipcxj
fonte