我们真的应该在onActivityCreated中调用getLoaderManager()。initLoader,这会导致onLoadFinished被调用两次

时间:2013-03-20 05:07:19

标签: android

Google建议我们在Fragment的getLoaderManager().initLoader(0, null, this);

中致电onActivityCreated

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

但是,这会产生以下问题: onLoadFinished将在配置更改期间调用两次(轮换)

我们可以按照以下方式模拟问题。

代码

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! -> 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

根据Android: LoaderCallbacks.OnLoadFinished called twice,建议的解决方案之一是在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! -> org.yccheok.gui.HomeMenuFragment$Infos@4195aed0


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

我想知道

  1. 为什么建议的解决方案有效?
  2. 这是一个错误吗?我们是否应该向Google提出有关此行为的错误?也许有一张票被提交给谷歌,但我找不到它。

2 个答案:

答案 0 :(得分:11)

为什么建议的解决方案正常运作

如果我们在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()

这是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); } 来电:

{{1}}

我认为很明显:)

答案 1 :(得分:-2)

尝试从onStartLoading中删除投放结果。当为已经返回的加载器调用LoaderManager时,initLoader已经返回现有值。