versiones pie oreo developer descargar android

android - pie - ¿Realmente deberíamos llamar a getLoaderManager(). InitLoader en onActivityCreated, lo que hace que se llame dos veces a onLoadFinished?



android pie (2)

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 produce el siguiente problema: se llamará dos veces a onLoadFinished durante los cambios de configuración (Rotación)

Podemos simular el problema de la siguiente manera.

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 se llama dos veces , una de las soluciones propuestas es llamar 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 se ve 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 boleto que se está presentando a Google, pero no puedo encontrarlo.

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


¿Por qué la solución propuesta está funcionando?

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

Como resultado tenemos la llamada mLoaderManager.doReportStart() 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 causa de la primera llamada de onLoadFinished() .

Más adelante en FragmentActivity.onStart() hemos llamado 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 causa de la segunda llamada de onLoadFinished() .

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

Si lo hacemos de esta manera, no tenemos 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; }

Puede ver la llamada a 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 :)