Tengo algunos ListFragments que usan un CursorLoader para recuperar su contenido. A medida que el usuario profundiza en el contenido, un Fragmento reemplaza a otro (la Actividad sigue siendo la misma). Pero si los cambios en el contenido en un fragmento no superior, la aplicación se bloquea:Android: CursorLoader se bloquea en el Fragmento no superior
E/AndroidRuntime(18830): FATAL EXCEPTION: main
E/AndroidRuntime(18830): java.lang.RuntimeException: Unable to resume activity {com.example.ExampleApp/com.example.ExampleApp.ExampleActivity}: java.lang.IllegalStateException: Content view not yet created
E/AndroidRuntime(18830): at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2120)
E/AndroidRuntime(18830): at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2135)
E/AndroidRuntime(18830): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:957)
E/AndroidRuntime(18830): at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(18830): at android.os.Looper.loop(Looper.java:130)
E/AndroidRuntime(18830): at android.app.ActivityThread.main(ActivityThread.java:3683)
E/AndroidRuntime(18830): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(18830): at java.lang.reflect.Method.invoke(Method.java:507)
E/AndroidRuntime(18830): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
E/AndroidRuntime(18830): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
E/AndroidRuntime(18830): at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime(18830): Caused by: java.lang.IllegalStateException: Content view not yet created
E/AndroidRuntime(18830): at android.support.v4.app.ListFragment.ensureList(ListFragment.java:328)
E/AndroidRuntime(18830): at android.support.v4.app.ListFragment.setListShown(ListFragment.java:280)
E/AndroidRuntime(18830): at android.support.v4.app.ListFragment.setListShownNoAnimation(ListFragment.java:266)
E/AndroidRuntime(18830): at com.example.ExampleApp.FirstListFragment.onLoadFinished(FirstListFragment.java:102)
E/AndroidRuntime(18830): at com.example.ExampleApp.FristListFragment.onLoadFinished(FirstListFragment.java:20)
E/AndroidRuntime(18830): at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:414)
E/AndroidRuntime(18830): at android.support.v4.app.LoaderManagerImpl$LoaderInfo.reportStart(LoaderManager.java:298)
E/AndroidRuntime(18830): at android.support.v4.app.LoaderManagerImpl.doReportStart(LoaderManager.java:751)
E/AndroidRuntime(18830): at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:512)
E/AndroidRuntime(18830): at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1129)
E/AndroidRuntime(18830): at android.app.Activity.performStart(Activity.java:3791)
E/AndroidRuntime(18830): at android.app.Activity.performRestart(Activity.java:3821)
E/AndroidRuntime(18830): at android.app.Activity.performResume(Activity.java:3826)
E/AndroidRuntime(18830): at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2110)
E/AndroidRuntime(18830): ... 10 more
Esto puede suceder cuando una actualización de fondo cambia el contenido, o cuando el usuario vuelve desde la pantalla de inicio y otro fragmento está en la cima de la pila posterior. El controlador onLoadFinished
intenta actualizar la interfaz de usuario, que ya no existe. Pero ¿por qué el fragmento sigue obteniendo actualizaciones de cursor de todos modos?
He solucionado el problema cancelando la actualización si el fragmento no está visible, pero parece una acción incorrecta.
Esto es lo que se ve mi fragmento tiene gusto, editado un poco por razones de brevedad:
public class FirstListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Initialize empty cursor adapter.
mAdapter = new ExampleCursorAdapter(getActivity(), null, 0);
setListAdapter(mAdapter);
// Start with a progress indicator
setListShown(false);
// Prepare the loader. Either re-connect with an existing one, or start a new one.
getLoaderManager().initLoader(EXAMPLE_LOADER_ID, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), ExampleProvider.CONTENT_URI, PROJECTION, null, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Do nothing if we're not visible.
if(!isVisible()) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
// The list should now be shown.
if(isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished() above is about to be
// closed. We need to make sure we are no longer using it.
mAdapter.swapCursor(null);
}
}
El cheque isVisible()
evita el choque, pero no lo es en cualquier parte del código ejemplo que he visto. ¿Qué pasa aquí?
Editar: Efectivamente, me terminó con un StaleDataException con una antigua vista de tratar de utilizar un cursor cerrado. Así que ahora estoy destruyendo el LoaderManager cuando se destruye la vista. Todavía no estoy seguro de si esto es lo correcto y no puedo reproducir StaleDataException.
Quité el truco isVisible()
desde arriba y añade esto:
@Override
public void onDestroyView() {
super.onDestroyView();
// The CursorLoader example doesn't do this, but if we get an update while the UI is
// destroyed, it will crash. Why is this necessary?
getLoaderManager().destroyLoader(EXAMPLE_LOADER_ID);
}
Sí, parece que hay algún error con el cargador que regresa demasiado pronto y getActivity() sigue siendo nulo (el fragmento no está completamente conectado). El cargador parece ser reiniciado por el framework a veces también. lo que hace que vuelva demasiado temprano. – pjco