Должен ли мы действительно называть getLoaderManager(). InitLoader в onActivityCreated, что приводит к тому, что onLoadFinished вызывается дважды
Google рекомендует нам позвонить getLoaderManager().initLoader(0, null, this);
внутри фрагмента onActivityCreated
http://developer.android.com/reference/android/content/AsyncTaskLoader.html
Однако это приводит к следующей проблеме: onLoadFinished будет вызываться дважды во время изменений конфигурации (Rotation)
Мы можем имитировать проблему следующим образом.
код
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();
}
Вход
I/HomeMenuFragment(14776): onActivityCreated
I/HomeMenuFragment(14776): forceLoad
I/HomeMenuFragment(14776): loadInBackground
I/HomeMenuFragment(14776): onLoadFinished! -> [email protected]
[Rotation happens right here]
I/HomeMenuFragment(14776): onActivityCreated
I/HomeMenuFragment(14776): onLoadFinished! -> [email protected]
I/HomeMenuFragment(14776): onLoadFinished! -> [email protected]
Согласно Android: LoaderCallbacks.OnLoadFinished вызывается дважды, одно из предлагаемых решений вызывает initLoader
в 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);
}
Вот журнал. Теперь он выглядит ОК после перемещения initLoader
до onResume
.
Вход
I/HomeMenuFragment(15468): onActivityCreated
I/HomeMenuFragment(15468): onResume
I/HomeMenuFragment(15468): forceLoad
I/HomeMenuFragment(15468): loadInBackground
I/HomeMenuFragment(15468): onLoadFinished! -> [email protected]
I/HomeMenuFragment(15468): onActivityCreated
I/HomeMenuFragment(15468): onResume
I/HomeMenuFragment(15468): onLoadFinished! -> [email protected]
Мне было интересно
- Почему предлагаемое решение работает?
- Это ошибка? Должны ли мы сообщать об ошибках Google в отношении этого поведения? Возможно, в Google есть билет, но я не могу его найти.
Ответы
Ответ 1
Почему предлагаемое решение работает
Если мы назовем getLoaderManager()
в onActivityCreated()
, то инициализируем переменную Fragment.mLoaderManager
.
В результате мы вызываем mLoaderManager.doReportStart()
в Fragment.performStart()
на 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();
}
}
Это причина первого вызова onLoadFinished()
.
Позже в FragmentActivity.onStart()
мы вызываем lm.finishRetain()
(см. фрагмент кода):
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();
}
}
}
Это причина второго вызова onLoadFinished()
.
OK. Теперь рассмотрим случай, когда мы называем getLoaderManager().initLoader(0, null, this)
в onResume()
:
Если мы это сделаем, у нас нет mLoaderManager.doReportStart()
и lm.finishRetain()
после onActivityCreated()
, но вместо этого у нас есть вызов onLoadFinished()
во время 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;
}
Вы можете увидеть вызов info.callOnLoadFinished()
в этом фрагменте:
if (info.mHaveData && mStarted) {
// If the loader has already generated its data, report it now.
info.callOnLoadFinished(info.mLoader, info.mData);
}
Я думаю, это ясно:)
Ответ 2
Попробуйте удалить результат доставки из onStartLoading
. LoaderManager
уже возвращает существующие значения при вызове initLoader
для уже загруженного загрузчика.