Fragmento MyFragment no adjunto a la actividad
Creé 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();
}
}
Agregué la Thread.sleep
parte para simular la descarga de datos. El código en onPostExecute
es para 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: Fragmento MyFragment{410f6060} no adjunto a la actividad
Creo que se debe a que MyFragment
mientras tanto se creó una nueva y se adjuntó a la Actividad antes de AsyncTask
finalizar. El código in onPostExecute
llama a un archivo MyFragment
.
¿Pero cómo puedo solucionar esto?
He encontrado la respuesta muy simple: isAdded()
:
Devuelve
true
si el fragmento está actualmente agregado a su actividad.
@Override
protected void onPostExecute(Void result){
if(isAdded()){
getResources().getString(R.string.app_name);
}
}
Para evitar onPostExecute
ser llamado cuando el Fragment
no está conectado al Activity
es cancelar el AsyncTask
al pausar o detener el Fragment
. Entonces ya isAdded()
no sería necesario. Sin embargo, es aconsejable mantener este control.
El problema es que estás intentando acceder a 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:
/**
* 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 su actividad.
Debido a que es posible que la actividad no esté adjunta, su llamada a getResources() generará una excepción.
En mi humilde opinión, la solución aceptada no es el camino a seguir, ya que simplemente estás ocultando el problema. La forma correcta es simplemente obtener los recursos de otro lugar cuya existencia siempre esté garantizada, como el contexto de la aplicación:
youApplicationObject.getResources().getString(...)
Me he enfrentado a dos escenarios diferentes aquí:
1) Cuando quiero que la tarea asincrónica finalice de todos modos: imagine 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 finalice de todos modos para tener los datos listos cuando llegue el usuario. 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 asincrónica finalice solo cuando se puedan actualizar las vistas: en el caso que estás proponiendo aquí, la tarea solo actualiza las vistas, no necesita almacenamiento de datos, por lo que no tiene idea de si la tarea finalizará 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 iniciar tareas desde la actividad en lugar de los fragmentos.
¡Ojalá esto ayude a alguien! :)