Fragmento MyFragment no adjunto a la actividad

He creado una pequeña aplicación de prueba que representa mi problema. Estoy usando ActionBarSherlock para implementar pestañas con (Sherlock) Fragmentos.

Mi código: TestActivity.java

 public class TestActivity extends SherlockFragmentActivity { private ActionBar actionBar; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupTabs(savedInstanceState); } private void setupTabs(Bundle savedInstanceState) { actionBar = getSupportActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); addTab1(); addTab2(); } private void addTab1() { Tab tab1 = actionBar.newTab(); tab1.setTag("1"); String tabText = "1"; tab1.setText(tabText); tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "1", MyFragment.class)); actionBar.addTab(tab1); } private void addTab2() { Tab tab1 = actionBar.newTab(); tab1.setTag("2"); String tabText = "2"; tab1.setText(tabText); tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "2", MyFragment.class)); actionBar.addTab(tab1); } } 

TabListener.java

 public class TabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener { private final SherlockFragmentActivity mActivity; private final String mTag; private final Class<T> mClass; public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz) { mActivity = activity; mTag = tag; mClass = clz; } /* The following are each of the ActionBar.TabListener callbacks */ public void onTabSelected(Tab tab, FragmentTransaction ft) { SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag); // Check if the fragment is already initialized if (preInitializedFragment == null) { // If not, instantiate and add it to the activity SherlockFragment mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName()); ft.add(android.R.id.content, mFragment, mTag); } else { ft.attach(preInitializedFragment); } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag); if (preInitializedFragment != null) { // Detach the fragment, because another one is being attached ft.detach(preInitializedFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { // User selected the already selected tab. Usually do nothing. } } 

MyFragment.java

 public class MyFragment extends SherlockFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { Thread.sleep(2000); } catch (InterruptedException ex) { } return null; } @Override protected void onPostExecute(Void result){ getResources().getString(R.string.app_name); } }.execute(); } } 

He añadido la parte Thread.sleep para simular la descarga de datos. El código en el onPostExecute es simular el uso del Fragment .

Cuando roto la pantalla muy rápidamente entre el paisaje y el retrato, recibo una excepción en el código onPostExecute :

Java.lang.IllegalStateException: Fragmento MyFragment {410f6060} no adjunto a la actividad

Creo que es porque un nuevo MyFragment se ha creado mientras tanto, y se adjuntó a la actividad antes de que el AsyncTask terminado. El código en onPostExecute llama a un MyFragment sin MyFragment .

Pero, ¿cómo puedo solucionar esto?

He encontrado la respuesta muy simple: isAdded() :

Devuelve true si el fragmento se agrega actualmente a su actividad.

 @Override protected void onPostExecute(Void result){ if(isAdded()){ getResources().getString(R.string.app_name); } } 

Para evitar que onPostExecute se llame cuando el Fragment no está conectado a la Activity se debe cancelar el AsyncTask al detener o detener el Fragment . Entonces isAdded() ya no sería necesario. Sin embargo, es recomendable mantener este control en su lugar.

He enfrentado dos escenarios diferentes aquí:

1) Cuando quiero que la tarea asíncrona termine de todos modos: imagina que mi onPostExecute almacena los datos recibidos y luego llama a un oyente para actualizar las vistas así que, para ser más eficiente, quiero que la tarea termine de todos modos así que tengo los datos listos cuando el usuario cames espalda. En este caso, normalmente hago esto:

 @Override protected void onPostExecute(void result) { // do whatever you do to save data if (this.getView() != null) { // update views } } 

2) Cuando quiero que la tarea asíncrona sólo termine cuando se pueden actualizar las vistas: el caso que usted está proponiendo aquí, la tarea sólo actualiza las vistas, no necesita almacenamiento de datos, por lo que no tiene idea de que la tarea termine si las vistas son Ya no se muestran. Hago esto:

 @Override protected void onStop() { // notice here that I keep a reference to the task being executed as a class member: if (this.myTask != null && this.myTask.getStatus() == Status.RUNNING) this.myTask.cancel(true); super.onStop(); } 

No he encontrado ningún problema con esto, aunque también uso una forma (quizás) más compleja que incluye el lanzamiento de tareas de la actividad en lugar de los fragmentos.

¡Deseo que esto ayude a alguien! 🙂

El problema con su código es la forma en la que está utilizando el AsyncTask, porque cuando gira la pantalla durante el hilo de suspensión:

 Thread.sleep(2000) 

El AsyncTask sigue funcionando, es porque no canceló correctamente la instancia de AsyncTask en onDestroy () antes de que el fragmento reconstruya (cuando gira) y cuando esta misma instancia de AsyncTask (después de rotar) ejecuta onPostExecute () Los recursos con getResources () con la instancia de fragmento antiguo (una instancia no válida):

 getResources().getString(R.string.app_name) 

Que es equivalente a:

 MyFragment.this.getResources().getString(R.string.app_name) 

Así que la solución final es administrar la instancia AsyncTask (para cancelar si esto sigue funcionando) antes de que el fragmento reconstruya al girar la pantalla, y si se cancela durante la transición, reinicie el AsyncTask después de la reconstrucción con la ayuda de un indicador booleano:

 public class MyFragment extends SherlockFragment { private MyAsyncTask myAsyncTask = null; private boolean myAsyncTaskIsRunning = true; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if(savedInstanceState!=null) { myAsyncTaskIsRunning = savedInstanceState.getBoolean("myAsyncTaskIsRunning"); } if(myAsyncTaskIsRunning) { myAsyncTask = new MyAsyncTask(); myAsyncTask.execute(); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean("myAsyncTaskIsRunning",myAsyncTaskIsRunning); } @Override public void onDestroy() { super.onDestroy(); if(myAsyncTask!=null) myAsyncTask.cancel(true); myAsyncTask = null; } public class MyAsyncTask extends AsyncTask<Void, Void, Void>() { public MyAsyncTask(){} @Override protected void onPreExecute() { super.onPreExecute(); myAsyncTaskIsRunning = true; } @Override protected Void doInBackground(Void... params) { try { Thread.sleep(2000); } catch (InterruptedException ex) {} return null; } @Override protected void onPostExecute(Void result){ getResources().getString(R.string.app_name); myAsyncTaskIsRunning = false; myAsyncTask = null; } } } 

El problema es que está intentando acceder a recursos (en este caso, cadenas) utilizando getResources (). GetString (), que intentará obtener los recursos de la Actividad. Vea este código fuente de la clase Fragment:

  /** * Return <code>getActivity().getResources()</code>. */ final public Resources getResources() { if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } return mHost.getContext().getResources(); } 

mHost es el objeto que contiene tu Actividad.

Debido a que la actividad no se puede adjuntar, su llamada getResources () lanzará una excepción.

La solución aceptada IMHO no es el camino a seguir, ya que está escondiendo el problema. La forma correcta es simplemente obtener los recursos de otro lugar que siempre está garantizado para existir, como el contexto de la aplicación:

 youApplicationObject.getResources().getString(...) 

Me enfrenté al mismo problema que acaba de añadir la instancia de singletone para obtener el recurso como se refiere por Erick

 MainFragmentActivity.defaultInstance().getResources().getString(R.string.app_name); 

también puedes usar

 getActivity().getResources().getString(R.string.app_name); 

Espero que esto sea de ayuda.

Su solución es bastante truco para esto y la fuga de fragmento de la actividad.

Así que en el caso de getResource o cualquier cosa que dependa del contexto de la actividad que accede desde Fragment siempre se verifica el estado de la actividad y el estado de los fragmentos como sigue

  Activity activity = getActivity(); if(activity != null && isAdded()) getResources().getString(R.string.no_internet_error_msg); //Or any other depends on activity context to be live like dailog } } 

Me enfrenté a problemas similares cuando la actividad de configuración de la aplicación con las preferencias cargadas era visible. Si cambiara una de las preferencias y luego hiciera girar el contenido de la pantalla y cambiara la preferencia nuevamente, se bloquearía con un mensaje de que el fragmento (mi clase Preferencias) no estaba conectado a una actividad.

Al depurar parecía que el método onCreate () del Fragmento de Preferencias se estaba llamando dos veces cuando el contenido de la pantalla giraba. Ya era bastante extraño. Entonces agregué la comprobación de isAdded () fuera del bloque donde indicaría el choque y solucionó el problema.

Aquí está el código del oyente que actualiza el resumen de preferencias para mostrar la nueva entrada. Se encuentra en el método onCreate () de mi clase Preferencias que extiende la clase PreferenceFragment:

 public static class Preferences extends PreferenceFragment { SharedPreferences.OnSharedPreferenceChangeListener listener; @Override public void onCreate(Bundle savedInstanceState) { // ... listener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { // check if the fragment has been added to the activity yet (necessary to avoid crashes) if (isAdded()) { // for the preferences of type "list" set the summary to be the entry of the selected item if (key.equals(getString(R.string.pref_fileviewer_textsize))) { ListPreference listPref = (ListPreference) findPreference(key); listPref.setSummary("Display file content with a text size of " + listPref.getEntry()); } else if (key.equals(getString(R.string.pref_fileviewer_segmentsize))) { ListPreference listPref = (ListPreference) findPreference(key); listPref.setSummary("Show " + listPref.getEntry() + " bytes of a file at once"); } } } }; // ... } 

Espero que esto ayude a otros!

 if (getActivity() == null) return; 

Funciona también en algunos casos. Simplemente rompe la ejecución del código y asegúrate de que la aplicación no se bloquee.

En mi caso, los métodos de fragmento han sido llamados después de

 getActivity().onBackPressed(); 

Un viejo post, pero me sorprendió la respuesta más votada.

La solución adecuada para esto debería ser cancelar el asynctask en onStop (o donde sea apropiado en su fragmento). De esta manera no introduces un escape de memoria (un asynctask manteniendo una referencia a tu fragmento destruido) y tienes un mejor control de lo que está pasando en tu fragmento.

 @Override public void onStop() { super.onStop(); mYourAsyncTask.cancel(true); } 

Si amplía la clase Application y mantiene un objeto Contexto 'global' estático, como se indica a continuación, puede utilizarlo en lugar de la actividad para cargar un recurso de cadena.

 public class MyApplication extends Application { public static Context GLOBAL_APP_CONTEXT; @Override public void onCreate() { super.onCreate(); GLOBAL_APP_CONTEXT = this; } } 

Si utiliza esto, puede salirse con la Toast y la carga de recursos sin preocuparse de ciclos de vida.

  • Android "Sólo el subproceso original que creó una jerarquía de vistas puede tocar sus vistas." Error en Fragmento
  • ¿Cómo puedo crear la superposición de tutoriales gris semi-transparente en Android?
  • ViewPager en blanco en la parte posterior de otro fragmento
  • Android: NullPointerException en DialogFragment.dismissInternal en DialogFragment.dismissAllow
  • ViewPager setCurrentItem Slide
  • Fragmento de Android pop de backstack con animación
  • Android listfragment onItemClick no funciona?
  • GetLayoutInflater () en el fragmento
  • Añadir un fragmento a un diálogo
  • Cómo transmitir el contexto de Fragment a la interfaz
  • Tengo un error extraño al usar un ViewPager dentro de un Fragmento
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.