He creado una pequeña aplicación de prueba que representa mi problema. Estoy usando ActionBarSherlock para implementar pestañas 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<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 agregado la Thread.sleep
parte para simular la descarga de datos. El código en el onPostExecute
es simular el uso de Fragment
.
Cuando giro la pantalla muy rápido entre horizontal y vertical, aparece una excepción en el onPostExecute
código:
java.lang.IllegalStateException: Fragment MyFragment {410f6060} no adjunto a la actividad
Creo que es porque MyFragment
se ha creado una nueva mientras tanto, y se adjuntó a la Actividad antes de AsyncTask
finalizar. El código en onPostExecute
llamadas a un desapegado MyFragment
.
Pero, ¿cómo puedo solucionar esto?
fuente
mView = inflater.inflate(R.layout.my_layout, container, false)
Y ahora utilizar este punto de vista cuando se quiere obtener recursos:mView.getResources().***
. Me ayudó a solucionar este error.Context
que está adjunto a su `mView`.mView
queda nulo en onDestroy?Respuestas:
He encontrado la respuesta muy simple
isAdded()
:Para evitar
onPostExecute
ser llamado cuando elFragment
no está conectado alActivity
es cancelar elAsyncTask
al pausar o detener elFragment
. EntoncesisAdded()
ya no sería necesario. Sin embargo, es recomendable mantener esta verificación en su lugar.fuente
isDetached()
se agregó en el nivel 13 de APIEl problema es que está intentando acceder a los recursos (en este caso, cadenas) usando getResources (). GetString (), que intentará obtener los recursos de la Actividad. Vea este código fuente de la clase Fragment:
mHost
es el objeto que contiene tu actividad.Debido a que la Actividad podría no estar adjunta, su llamada a getResources () generará 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 obtener los recursos de otro lugar que siempre se garantiza que existe, como el contexto de la aplicación:
fuente
getString()
cuando mi fragmento estaba en pausa. GraciasMe he enfrentado a dos escenarios diferentes aquí:
1) Cuando quiero que la tarea asincrónica termine de todos modos: imagina que mi onPostExecute almacena los datos recibidos y luego llama a un oyente para actualizar las vistas, por lo que, para ser más eficiente, quiero que la tarea termine de todos modos para tener los datos listos cuando el usuario llegue espalda. En este caso, generalmente hago esto:
2) Cuando quiero que la tarea asincrónica solo termine cuando las vistas se pueden actualizar: en 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:
No he encontrado ningún problema con esto, aunque también uso una (quizás) forma más compleja que incluye iniciar tareas desde la actividad en lugar de los fragmentos.
Ojalá esto ayude a alguien! :)
fuente
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:
AsyncTask todavía funciona, es porque no canceló la instancia de AsyncTask correctamente en onDestroy () antes de que se reconstruya el fragmento (cuando gira) y cuando esta misma instancia de AsyncTask (después de girar) se ejecuta en PostExecute (), intenta encontrar los recursos con getResources () con la antigua instancia de fragmento (una instancia no válida):
que es equivalente a:
Entonces, la solución final es administrar la instancia de AsyncTask (para cancelar si aún funciona) antes de que el fragmento se reconstruya cuando gira 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:
fuente
getResources().***
usaFragments.this.getResource().***
ayudaSon una solución bastante engañosa para esto y una fuga de fragmentos de la actividad.
Entonces, en el caso de getResource o cualquier cosa que dependa del acceso al contexto de actividad desde Fragment, siempre se verifica el estado de la actividad y el estado de los fragmentos de la siguiente manera
fuente
isAdded
es suficiente. Nunca vi una situación cuando segetString()
había estrellado siisAdded == true
. ¿Estás seguro de que se mostró una actividad y se adjuntó un fragmento?Funciona también en algunos casos. Simplemente interrumpe la ejecución del código y asegúrese de que la aplicación no se bloquee
fuente
Me enfrenté al mismo problema, solo agregué la instancia de singletone para obtener el recurso según lo referido por Erick
también puedes usar
Espero que esto sea de ayuda.
fuente
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 volviera a cambiar la preferencia, 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 el contenido de la pantalla giraba. 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:
¡Espero que esto ayude a otros!
fuente
Si extiende la
Application
clase y mantiene un objeto de contexto 'global' estático, como se indica a continuación, puede usar eso en lugar de la actividad para cargar un recurso de cadena.Si usa esto, puede salirse con la suya
Toast
y cargar recursos sin preocuparse por los ciclos de vida.fuente
En mi caso, se han llamado métodos de fragmentos después
fuente
Una publicación anterior, pero me sorprendió la respuesta más votada.
La solución adecuada para esto debería ser cancelar el asinctask en onStop (o donde sea apropiado en su fragmento). De esta manera, no introduce una pérdida de memoria (un asinctask que mantiene una referencia a su fragmento destruido) y tiene un mejor control de lo que está sucediendo en su fragmento.
fuente
cancel
no puede evitar queonPostExecute
se invoque.