android android-fragments actionbarsherlock

android - Fragmento MiFragmento no adjunto a la Actividad



android-fragments actionbarsherlock (11)

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

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 onPostExecute es simular el uso del Fragment .

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

java.lang.IllegalStateException: Fragmento MyFragment {410f6060} no adjunto a la Actividad

Creo que se debe a que se ha creado un nuevo MyFragment mientras tanto, y se adjuntó a la Actividad antes de que finalizara la AsyncTask . El código en onPostExecute llama a un MyFragment sin MyFragment .

Pero, ¿cómo puedo solucionar esto?


El problema con tu código es la forma en que estás usando la AsyncTask, porque cuando giras la pantalla durante el hilo de suspensión:

Thread.sleep(2000)

AsyncTask sigue funcionando, es porque no canceló la instancia de AsyncTask correctamente en onDestroy () antes de que el fragmento se reconstruya (cuando se gire) y cuando esta misma instancia de AsyncTask (después de rotate) se ejecuta enPostExecute (), esto intenta encontrar los recursos con getResources () con la instancia de fragmento anterior (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 de AsyncTask (para cancelar si esto todavía funciona) antes de que el fragmento se vuelva a generar cuando gire 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<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 los recursos (en este caso, cadenas) utilizando getResources (). GetString (), que intentará obtener los recursos de la Actividad. Vea este código fuente de la clase Fragmento:

/** * 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 podría no estar adjunta, su llamada a getResources () lanzará una Excepción.

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

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


En mi caso los métodos de fragmentación han sido llamados después.

getActivity().onBackPressed();


He encontrado la respuesta muy simple: isAdded() :

Devuelva 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 cuando se detiene o detiene 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 asincrónica termine de todos modos: imagina que onPostExecute almacena los datos recibidos y luego llama a un oyente para que actualice las vistas, para que sea más eficiente, quiero que la tarea termine de todos modos, así que tengo la información lista cuando el usuario lo indique. atrás. En este caso suelo hacer 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 termine cuando las vistas pueden actualizarse: en el caso que está proponiendo aquí, la tarea solo actualiza las vistas, no se necesita almacenamiento de datos, por lo que no tiene la menor 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 desde la actividad en lugar de los fragmentos.

¡Ojalá esto ayude a alguien! :)


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

Cuando se hacía la depuración, se parecía al método onCreate () del PreferencesFragment cuando se giraba el contenido de la pantalla. Eso ya era bastante extraño. Luego agregué la verificación isAdded () fuera del bloque donde indicaría el bloqueo y resolvió 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 de 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!


Me enfrenté con el mismo problema, solo agregué la instancia de singletone para obtener el recurso referido 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.


Si extiende la clase de Application y mantiene un objeto de contexto ''global'' estático, de la siguiente manera, puede usar eso 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 la carga de recursos sin preocuparse por los ciclos de vida.


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

La solución adecuada para esto debería ser cancelar la asincronización en onStop (o donde sea apropiado en su fragmento). De esta manera, no introduce una pérdida de memoria (una asincreta que guarda 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); }


Son una solución bastante difícil para esto y la pérdida de fragmentos de la actividad.

Por lo tanto, en el caso de getResource o cualquier otro que dependa del contexto de actividad al que se acceda 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 } }


if (getActivity() == null) return;

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