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