Fragmento de MyFragment no asociado a Activity

Creé una pequeña aplicación de prueba que representa mi problema. Estoy usando ActionBarSherlock para implementar tabs con (Sherlock) Fragments.

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(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(TestActivity.this, "2", MyFragment.class)); actionBar.addTab(tab1); } } 

TabListener.java

 public class TabListener implements com.actionbarsherlock.app.ActionBar.TabListener { private final SherlockFragmentActivity mActivity; private final String mTag; private final Class mClass; public TabListener(SherlockFragmentActivity activity, String tag, Class 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() { @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(); } } 

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

Cuando giro la pantalla muy rápido entre horizontal y vertical, onPostExecute una Excepción en el código onPostExecute :

java.lang.IllegalStateException: Fragmento MyFragment {410f6060} no asociado a Activity

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

¿Pero cómo puedo arreglar 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 se onPostExecute a onPostExecute cuando el Fragment no está adjunto a la Activity se debe cancelar la AsyncTask al detener o detener el Fragment . Entonces isAdded() ya no sería necesario. Sin embargo, es aconsejable mantener este control en su lugar.

Me he enfrentado a dos escenarios diferentes aquí:

1) Cuando quiero que la tarea asíncrona termine de todos modos: imagine que my onPostExecute almacena los datos recibidos y luego llama a un oyente para actualizar las vistas, para que sea más eficiente, quiero que la tarea finalice de todos modos, de modo que tenga los datos listos cuando el usuario responda espalda. En este caso, generalmente 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 solo finalice cuando las vistas se pueden actualizar: el caso que está proponiendo aquí, la tarea solo actualiza las vistas, no se necesita almacenamiento de datos, por lo que no tiene idea de que la tarea finalice si las vistas son ya no se muestra. 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 inicio de tareas de la actividad en lugar de los fragmentos.

¡Ojalá esto ayude a alguien! 🙂

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

  /** * Return getActivity().getResources(). */ 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.

Como es posible que la actividad no esté adjunta, su llamada getResources () emitirá una excepción.

La solución aceptada en mi humilde opinión no es el camino a seguir ya que solo está ocultando el problema. La forma correcta es simplemente obtener los recursos de otro lugar que siempre está garantizado que existe, como el contexto de la aplicación:

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

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

 Thread.sleep(2000) 

AsyncTask sigue funcionando, es porque no canceló correctamente la instancia AsyncTask en onDestroy () antes de que el fragmento se reconstruya (cuando gira) y cuando esta misma instancia AsyncTask (después de rotar) se ejecuta en PostExecute (), esto intenta encontrar los recursos con getResources () con la antigua instancia de fragmento (una instancia no válida):

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

que es equivalente a:

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

Entonces, la solución final es administrar la instancia de AsyncTask (para cancelar si todavía está funcionando) antes de que el fragmento se reconstruya al rotar la pantalla, y si se cancela durante la transición, reinicie AsyncTask después de la reconstrucción con la ayuda de una bandera booleana:

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

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

Entonces, en el caso de getResource o cualquier cosa que dependa del contexto de actividad accediendo desde Fragment, siempre se verifica el estado de la actividad y el estado de los fragmentos de la siguiente manera

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

Enfrenté el mismo problema, solo agregué la instancia de singletone para obtener el recurso como lo menciona 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.

 if (getActivity() == null) return; 

funciona también en algunos casos. Simplemente rompe la ejecución del código y se asegura de que la aplicación no se cuelgue

Tuve 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 que el contenido de la pantalla girara y cambiara la preferencia de nuevo, se bloquearía con un mensaje de que el fragmento (mi clase de Preferencias) no estaba adjunto a una actividad.

Al depurar parecía que el método onCreate () del PreferencesFragment se llamaba dos veces cuando se giraba el contenido de la pantalla. Eso ya era bastante extraño. Luego agregué el control isAdded () fuera del bloque donde indicaría el locking y resolvió el problema.

Aquí está el código del oyente que actualiza el resumen de preferencias para mostrar la nueva entrada. Está ubicado en el método onCreate () de mi clase Preferences 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!

Si amplía la clase de Application y mantiene un objeto de contexto ‘global’ estático, de la siguiente manera, puede usarlo 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 usa esto, puede salirse con Toast y cargar recursos sin preocuparse por los ciclos de vida.

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

 getActivity().onBackPressed(); 

Una publicación anterior, pero me sorprendió la respuesta más votada.

La solución adecuada para esto debería ser cancelar la asynctask en onStop (o donde sea apropiado en su fragmento). De esta forma, no introduce una pérdida de memoria (una tarea asíncrona que mantiene una referencia a su fragmento destruido) y tiene un mejor control de lo que está sucediendo en su fragmento.

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