¿Por qué onLoadFinished se llama de nuevo después de que se reanude el fragmento?

Tengo un problema peculiar con los cargadores. Actualmente no estoy seguro de si se trata de un error en mi código o malentendido cargadores.

La aplicación

El problema surge con las conversaciones (imagine algo similar a Whatsapp). Los cargadores que utilizo se implementan basados ​​en el ejemplo de AsyncTaskLoader . Estoy usando la biblioteca de soporte.

  • En OnCreate, inicio un cargador para recuperar los mensajes en caché.
  • Cuando CachedMessageLoader finaliza, inicia un RefreshLoader para recuperar (en línea) los mensajes más recientes.
  • Cada tipo de cargador como un ID distinto (por ejemplo, sin conexión: 1 en línea: 2)

Esto funciona muy bien, con la siguiente excepción.

Problema

Cuando abro otro fragmento (y agrego la transacción a la backstack) y luego utilizo la Back-Key para volver al onLoadFinished conversación, onLoadFinished se vuelve a llamar con ambos resultados anteriores. Esta llamada sucede antes de que el fragmento haya tenido alguna oportunidad de iniciar un cargador de nuevo …

Esta entrega de "antiguos" resultados que he obtenido antes de los resultados en los mensajes duplicados.

Pregunta

  • ¿Por qué se entregan nuevamente esos resultados?
  • ¿Puedo usar estos cargadores mal?
  • ¿Puedo "invalidar" los resultados para asegurarme de que solo los entregue una vez o debo eliminar los duplicados?

Traza de la pila de la llamada

 MyFragment.onLoadFinished(Loader, Result) line: 369 MyFragment.onLoadFinished(Loader, Object) line: 1 LoaderManagerImpl$LoaderInfo.callOnLoadFinished(Loader, Object) line: 427 LoaderManagerImpl$LoaderInfo.reportStart() line: 307 LoaderManagerImpl.doReportStart() line: 768 MyFragment(Fragment).performStart() line: 1511 FragmentManagerImpl.moveToState(Fragment, int, int, int, boolean) line: 957 FragmentManagerImpl.moveToState(int, int, int, boolean) line: 1104 BackStackRecord.popFromBackStack(boolean) line: 764 ... 

Actualización 1 Los cargadores mencionados aquí son iniciados por el fragmento de conversación:

 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); Bundle args = getArguments(); m_profileId = args.getString(ArgumentConstants.ARG_USERID); m_adapter = new MessageAdapter(this); if (savedInstanceState != null) { restoreInstanceState(savedInstanceState); } if (m_adapter.isEmpty()) { Bundle bundle = new Bundle(); bundle.putString(ArgumentConstants.ARG_USERID, m_profileId); getLoaderManager().restartLoader(R.id.loader_message_initial, bundle, this); } else { // Omitted: Some arguments passed in Bundle Bundle b = new Bundle(). getLoaderManager().restartLoader(R.id.loader_message_refresh, b, this); } } @Override public void onResume() { super.onResume(); // Omitted: setting up UI state / initiating other loaders that work fine } @Override public AbstractMessageLoader onCreateLoader(final int type, final Bundle bundle) { final SherlockFragmentActivity context = getSherlockActivity(); context.setProgressBarIndeterminateVisibility(true); switch (type) { case R.id.loader_message_empty: return new EmptyOnlineLoader(context, bundle); case R.id.loader_message_initial: return new InitialDBMessageLoader(context, bundle); case R.id.loader_message_moreoldDB: return new OlderMessageDBLoader(context, bundle); case R.id.loader_message_moreoldOnline: return new OlderMessageOnlineLoader(context, bundle); case R.id.loader_message_send: sendPreActions(); return new SendMessageLoader(context, bundle); case R.id.loader_message_refresh: return new RefreshMessageLoader(context, bundle); default: throw new UnsupportedOperationException("Unknown loader"); } } @Override public void onLoadFinished(Loader<Holder<MessageResult>> loader, Holder<MessageResult> holder) { if (getSherlockActivity() != null) { getSherlockActivity().setProgressBarIndeterminateVisibility(false); } // Omitted: Error handling of result (can contain exception) List<PrivateMessage> unreadMessages = res.getUnreadMessages(); switch (type) { case R.id.loader_message_moreoldDB: { // Omitted error handling (no data) if (unreadMessages.isEmpty()) { m_hasNoMoreCached = true; // Launch an online loader Bundle b = new Bundle(); // Arguments omitted getLoaderManager().restartLoader(R.id.loader_message_moreoldOnline, b, ConversationFragment.this); } // Omitted: Inserting results into adapter } case R.id.loader_message_empty: { // Online load when nothing in DB // Omitted: error/result handling handling break; } case R.id.loader_message_initial: { // Latest from DB, when opening // Omitted: Error/result handling // If we found nothing, request online if (unreadMessages.isEmpty()) { Bundle b = new Bundle(); // Omitted: arguments getLoaderManager().restartLoader(R.id.loader_message_empty, b, this); } else { // Just get new stuff Bundle b = new Bundle(); // Omitted: Arguments getLoaderManager().restartLoader(R.id.loader_message_refresh, b, this); } break; } // Omitted: Loaders that do not start other loaders, but only add returned data to the adapter default: throw new IllegalArgumentException("Unknown loader type " + type); } // Omitted: Refreshing UI elements } @Override public void onLoaderReset(Loader<Holder<MessageResult>> arg0) { } 

Actualizar 2 My MainActivity (que ultimatively alberga todos los fragmentos) subclases SherlockFragmentActivity y básicamente lanza fragmentos como este:

  Fragment f = new ConversationFragment(); // Setup omitted f.setRetainInstance(false); // Omitted: Code related to navigation drawer FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction().replace(R.id.fragment_container_frame, f).commit(); 

El fragmento de conversación inicia el fragmento de "perfil de visualización" de la siguiente manera:

 DisplayProfileFragment f = new DisplayProfileFragment(); // Arguments omitted FragmentManager manager = getSherlockActivity().getSupportFragmentManager(); FragmentTransaction transaction = manager.beginTransaction(); transaction.replace(R.id.fragment_container_frame, f).addToBackStack(null).commit(); 

Hay otras preguntas similares, como Android: LoaderCallbacks.OnLoadFinished llamado dos veces Sin embargo, el comportamiento de los ganchos gestor de cargador son lo que son. Puede destruir el cargador después de obtener el primer conjunto de resultados

 public abstract void destroyLoader (int id) 

O puede manejar el onLoaderReset y vincular sus datos de interfaz de usuario más de cerca a los datos del cargador

 public abstract void onLoaderReset (Loader<D> loader) 

Se llama cuando un cargador creado previamente se está restableciendo y, por lo tanto, hace que sus datos no estén disponibles. La aplicación debe en este momento eliminar cualquier referencia que tenga a los datos del cargador.

Personalmente, utilizaría un ContentProvider y un CursorLoader para esto (cada fila de datos necesitaría tener un _ID único pero para los mensajes que no deben ser un problema).

  • Paquete de compatibilidad de Android - fragmento ... no conectado a la actividad
  • Filtro ListView con CursorLoader y CursorAdapter personalizado
  • Android TabsAdapter con ActionbarSherlock
  • ¿OnLoadFinished () es asíncrono (subproceso de fondo)?
  • Refrescar una vista dentro de un fragmento
  • Cargadores en Android Honeycomb
  • SherlockFragmentActivity con múltiples ListFragments y batallas con cursor SQLite
  • ¿Cuál es el alcance de LoaderManager?
  • Poblar listview de ListAdapter o SimpleCursorAdapter
  • AsyncTaskLoader vs AsyncTask
  • Global Loader (LoaderManager) para su reutilización en múltiples Actividades / Fragmentos
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.