Подтвердить что ты не робот

Должен ли мы действительно называть 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 есть билет, но я не могу его найти.
4b9b3361

Ответ 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 для уже загруженного загрузчика.