¿Deberíamos llamar realmente getLoaderManager (). InitLoader en onActivityCreated, lo que hace que onLoadFinished sea llamado dos veces

Google nos recomienda llamar a getLoaderManager().initLoader(0, null, this); Dentro de Fragment's onActivityCreated

Http://developer.android.com/reference/android/content/AsyncTaskLoader.html

Sin embargo, esto genera el siguiente problema: onLoadFinished se llamará dos veces durante los cambios de configuración (rotación)

Podemos simular el problema como sigue.

Código

 package org.yccheok.gui; import android.content.Context; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.Loader; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.actionbarsherlock.app.SherlockFragment; public class HomeMenuFragment extends SherlockFragment implements LoaderManager.LoaderCallbacks<HomeMenuFragment.Infos> { private static class InfosLoader extends AsyncTaskLoader<Infos> { private Infos infos = null; public InfosLoader(Context context) { super(context); } @Override public Infos loadInBackground() { Log.i(TAG, "loadInBackground"); this.infos = Infos.newInstance(); return infos; } /** * Handles a request to cancel a load. */ @Override public void onCanceled(Infos infos) { super.onCanceled(infos); } /** * Handles a request to stop the Loader. * Automatically called by LoaderManager via stopLoading. */ @Override protected void onStopLoading() { // Attempt to cancel the current load task if possible. cancelLoad(); } /** * Handles a request to start the Loader. * Automatically called by LoaderManager via startLoading. */ @Override protected void onStartLoading() { if (this.infos != null) { Log.i(TAG, "deliverResult"); deliverResult(this.infos); } if (takeContentChanged() || this.infos == null) { Log.i(TAG, "forceLoad"); forceLoad(); } } /** * Handles a request to completely reset the Loader. * Automatically called by LoaderManager via reset. */ @Override protected void onReset() { super.onReset(); // Ensure the loader is stopped onStopLoading(); // At this point we can release the resources associated with 'apps' // if needed. this.infos = null; } } static class Infos { private Infos() { } public static Infos newInstance() { return new Infos(); } } @Override public void onActivityCreated (Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Log.i(TAG, "onActivityCreated"); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } @Override public Loader<Infos> onCreateLoader(int arg0, Bundle arg1) { return new InfosLoader(this.getSherlockActivity()); } @Override public void onLoadFinished(Loader<Infos> arg0, Infos arg1) { Log.i(TAG, "onLoadFinished! -> " + arg1); } @Override public void onLoaderReset(Loader<Infos> arg0) { } public void reloadAfterOpenFromCloud() { this.getLoaderManager().getLoader(0).onContentChanged(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.home_menu, container, false); return v; } private static final String TAG = HomeMenuFragment.class.getSimpleName(); } 

Explotación florestal

 I/HomeMenuFragment(14776): onActivityCreated I/HomeMenuFragment(14776): forceLoad I/HomeMenuFragment(14776): loadInBackground I/HomeMenuFragment(14776): onLoadFinished! -> org.yccheok.gui.HomeMenuFragment$Infos@4195ad58 [Rotation happens right here] I/HomeMenuFragment(14776): onActivityCreated I/HomeMenuFragment(14776): onLoadFinished! -> org.yccheok.gui.HomeMenuFragment$Infos@4195ad58 I/HomeMenuFragment(14776): onLoadFinished! -> org.yccheok.gui.HomeMenuFragment$Infos@4195ad58 

Según Android: LoaderCallbacks.OnLoadFinished llamado dos veces , uno de la solución propuesta está llamando a initLoader en onResume .

 @Override public void onActivityCreated (Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Log.i(TAG, "onActivityCreated"); //getLoaderManager().initLoader(0, null, this); } @Override public void onResume() { super.onResume(); Log.i(TAG, "onResume"); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } 

Aquí está el registro. Ahora parece que está bien después de mover initLoader a onResume .

Explotación florestal

 I/HomeMenuFragment(15468): onActivityCreated I/HomeMenuFragment(15468): onResume I/HomeMenuFragment(15468): forceLoad I/HomeMenuFragment(15468): loadInBackground I/HomeMenuFragment(15468): onLoadFinished! -> org.yccheok.gui.HomeMenuFragment$Infos@4195aed0 I/HomeMenuFragment(15468): onActivityCreated I/HomeMenuFragment(15468): onResume I/HomeMenuFragment(15468): onLoadFinished! -> org.yccheok.gui.HomeMenuFragment$Infos@4195aed0 

me preguntaba

  1. ¿Por qué funciona la solución propuesta?
  2. ¿Es esto un error? ¿Debemos presentar un error a Google con respecto a este comportamiento? Tal vez hay un ticket que se está archivando en Google, pero no lo encuentro.

Por qué la solución propuesta funciona

Si llamamos a getLoaderManager() en onActivityCreated() entonces inicializamos la variable Fragment.mLoaderManager .

Como resultado tenemos mLoaderManager.doReportStart() llamada en Fragment.performStart() en FragmentActivity.onStart() :

  void performStart() { if (mChildFragmentManager != null) { mChildFragmentManager.noteStateNotSaved(); mChildFragmentManager.execPendingActions(); } mCalled = false; onStart(); if (!mCalled) { throw new SuperNotCalledException("Fragment " + this + " did not call through to super.onStart()"); } if (mChildFragmentManager != null) { mChildFragmentManager.dispatchStart(); } if (mLoaderManager != null) { mLoaderManager.doReportStart(); } } 

Es la causa de la primera llamada de onLoadFinished() .

Más adelante en FragmentActivity.onStart() tenemos una llamada a lm.finishRetain() (ver fragmento de código):

  if (mAllLoaderManagers != null) { LoaderManagerImpl loaders[] = new LoaderManagerImpl[mAllLoaderManagers.size()]; mAllLoaderManagers.values().toArray(loaders); if (loaders != null) { for (int i=0; i<loaders.length; i++) { LoaderManagerImpl lm = loaders[i]; lm.finishRetain(); lm.doReportStart(); } } } 

Es la causa de la segunda llamada de onLoadFinished() .


DE ACUERDO. Ahora considere el caso cuando llamamos getLoaderManager().initLoader(0, null, this) in onResume() :

Si lo hacemos de esta manera, no tenemos ni mLoaderManager.doReportStart() ni lm.finishRetain() después de onActivityCreated() , sino que tenemos onLoadFinished() llamada initLoader() durante initLoader() :

 public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { if (mCreatingLoader) { throw new IllegalStateException("Called while creating a loader"); } LoaderInfo info = mLoaders.get(id); if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args); if (info == null) { // Loader doesn't already exist; create. info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); if (DEBUG) Log.v(TAG, " Created new loader " + info); } else { if (DEBUG) Log.v(TAG, " Re-using existing loader " + info); info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; } if (info.mHaveData && mStarted) { // If the loader has already generated its data, report it now. info.callOnLoadFinished(info.mLoader, info.mData); } return (Loader<D>)info.mLoader; } 

Puedes ver la llamada de info.callOnLoadFinished() en este fragmento:

 if (info.mHaveData && mStarted) { // If the loader has already generated its data, report it now. info.callOnLoadFinished(info.mLoader, info.mData); } 

Creo que está claro

Intente eliminar el resultado de entrega de su onStartLoading . LoaderManager ya devuelve los valores existentes cuando se llama a initLoader para un cargador que ya ha regresado.

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.