如果我们真的叫getLoaderManager()。initLoader在onActivityCreated,导致onLoadFinished被调用两次两次、initLoader、getLoaderM

2023-09-06 07:31:48 作者:給你零度的愛情i

谷歌建议我们叫 getLoaderManager()initLoader(0,空,这一点); 中的片段的 onActivityCreated

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

不过,这会产生以下问题: onLoadFinished期间配置的变化(旋转)将被调用两次

我们可以模拟这个问题如下。

code

 包org.yccheok.gui;

进口android.content.Context;
进口android.os.Bundle;
进口android.support.v4.app.LoaderManager;
进口android.support.v4.content.AsyncTaskLoader;
进口android.support.v4.content.Loader;
进口android.util.Log;
进口android.view.LayoutInflater;
进口android.view.View;
进口android.view.ViewGroup;

进口com.actionbarsherlock.app.SherlockFragment;

公共类HomeMenuFragment扩展SherlockFragment实现LoaderManager.LoaderCallbacks< HomeMenuFragment.Infos> {
    私有静态类InfosLoader扩展AsyncTaskLoader<相关信息> {

        私人的相关信息的相关信息= NULL;

        公共InfosLoader(上下文的背景下){
            超(上下文);
        }

        @覆盖
        公开相关信息loadInBackground(){
            Log.i(TAG,loadInBackground);

            this.infos = Infos.newInstance();
            返回的相关信息;
        }

        / **
         *处理的请求取消负载。
         * /
        @覆盖
        公共无效onCanceled(相关信息的相关信息){
            super.onCanceled(相关信息);
        }

        / **
         *处理的请求停止加载程序。
         *自动由LoaderManager通过stopLoading调用。
         * /
        @覆盖
        保护无效onStopLoading(){
            //尝试如果可能的话,取消当前的负载任务。
            cancelLoad();
        }

        / **
         *处理的请求启动加载器。
         *自动由LoaderManager通过startLoading调用。
         * /
        @覆盖
        保护无效onStartLoading(){
            如果(this.infos!= NULL){
                Log.i(TAG,deliverResult);
                deliverResult(this.infos);
            }

            如果(takeContentChanged()|| this.infos == NULL){
                Log.i(TAG的forceload);
                的forceload();
            }
        }

        / **
         *处理的请求完全复位的装载机。
         *自动由LoaderManager通过复位调用。
         * /
        @覆盖
        保护无效onReset(){
            super.onReset();

            //确保装载机被停止
            onStopLoading();

            //在这一点上,我们可以释放与应用程序相关的资源
            // 如果需要的话。
            this.infos = NULL;
        }
    }

    静态类的相关信息{

        私有的相关信息(){
        }

        公共静态的相关信息的newInstance(){
            返回新的相关信息();
        }
    }

    @覆盖
    公共无效onActivityCreated(包savedInstanceState){
        super.onActivityCreated(savedInstanceState);
        Log.i(TAG,onActivityCreated);
        // prepare装载机。无论是与现有的一个重新连接,
        //或者开始一个新的。
        getLoaderManager()initLoader(0,空,这一点)。
    }

    @覆盖
    公共装载机<相关信息> onCreateLoader(INT为arg0,捆绑ARG1){
        返回新InfosLoader(this.getSherlockActivity());
    }

    @覆盖
    公共无效onLoadFinished(装载机<相关信息>为arg0,相关信息ARG1){
        Log.i(!onLoadFinished  - >中TAG,+ ARG1);
    }

    @覆盖
    公共无效onLoaderReset(装载机<相关信息>为arg0){
    }

    公共无效reloadAfterOpenFromCloud(){
        。this.getLoaderManager()getLoader(0).onContentChanged();
    }

    @覆盖
    公共查看onCreateView(LayoutInflater充气,容器的ViewGroup,
            捆绑savedInstanceState){
        视图V = inflater.inflate(R.layout.home_menu,集装箱,假);
        返回伏;
    }

    私有静态最后字符串变量= 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

[旋转发生在这里]

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叫了两声的,提出的解决方案之一是调用 initLoader onResume

  @覆盖
公共无效onActivityCreated(包savedInstanceState){
    super.onActivityCreated(savedInstanceState);
    Log.i(TAG,onActivityCreated);
    //getLoaderManager().initLoader(0,空,这一点);
}

@覆盖
公共无效onResume()
{
    super.onResume();
    Log.i(TAG,onResume);
    // prepare装载机。无论是与现有的一个重新连接,
    //或者开始一个新的。
    getLoaderManager()initLoader(0,空,这一点)。
}
 

下面是记录。它看起来确定后,现在我们移动 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
 

我想知道

为什么要提出的解决方案的工作? 这是一个错误?我们是否应该提交一份关于这种行为的错误,以谷歌?也许有被提交到谷歌一票,但我找不到它。 解决方案

为什么要提出的解决方案正在

如果我们调用getLoaderManager()的onActivityCreated(),然后我们初始化变量Fragment.mLoaderManager。

作为结果,我们有mLoaderManager.doReportStart()的Fragment.performStart()上FragmentActivity.onStart()调用:

 无效performStart(){
    如果(mChildFragmentManager!= NULL){
        mChildFragmentManager.noteStateNotSaved();
        mChildFragmentManager.execPendingActions();
    }
    mCalled = FALSE;
    ONSTART();
    如果(!mCalled){
        抛出新SuperNotCalledException(碎片+本
                +没有要求通过对super.onStart());
    }
    如果(mChildFragmentManager!= NULL){
        mChildFragmentManager.dispatchStart();
    }
    如果(mLoaderManager!= NULL){
        mLoaderManager.doReportStart();
    }
}
 

这是onLoadFinished的第一个电话的原因。

后来在FragmentActivity.onStart(),我们必须调用lm.finishRetain()(见code段):

 如果(mAllLoaderManagers!= NULL){
     LoaderManagerImpl装载机[] =新LoaderManagerImpl [mAllLoaderManagers.size()];
     。mAllLoaderManagers.values​​()的toArray(装载机);
     如果(装载机!= NULL){
         的for(int i = 0; I< loaders.length;我++){
             LoaderManagerImpl LM =装载机[I]
             lm.finishRetain();
             lm.doReportStart();
         }
     }
 }
 

这是onLoadFinished的第二个电话的原因。

确定。现在考虑当我们调用getLoaderManager()。initLoader(0,空,这一点)在onResume()的情况。

如果我们做这种方式,我们没有,也没有mLoaderManager.doReportStart(),也不lm.finishRetain()onActivityCreated()之后,但我们initLoader(在onLoadFinished调用)来代替:

 公开< D​​>装载机< D​​> initLoader(INT ID,捆绑的args,LoaderManager.LoaderCallbacks< D​​>回调){
    如果(mCreatingLoader){
        抛出新IllegalStateException异常(同时创造一个装载机名为);
    }

    的LoaderInfo信息= mLoaders.get(ID);

    如果(调试)Log.v(TAG,initLoader中的+本+参数:args =+参数);

    如果(资讯== NULL){
        //装载机已经不存在;创建。
        信息= createAndInstallLoader(ID,指定参数时,(LoaderManager.LoaderCallbacks<对象>)回调);
        如果(调试)Log.v(TAG,创建新的装载机+信息);
    } 其他 {
        如果(调试)Log.v(TAG,重新使用现有的装载机+信息);
        info.mCallbacks =(LoaderManager.LoaderCallbacks<对象>)的回调;
    }

    如果(info.mHaveData&安培;&安培; mStarted){
        //如果加载器已经生成的数据,现在的报告。
        info.callOnLoadFinished(info.mLoader,info.mData);
    }

    返程(装载机< D​​>)info.mLoader;
}
 

您可以看到info.callOnLoadFinished()调用这个片断:

 如果(info.mHaveData&安培;&安培; mStarted){
     //如果加载器已经生成的数据,现在的报告。
     info.callOnLoadFinished(info.mLoader,info.mData);
}
 

我认为这是明显的:)

Google recommends us to call getLoaderManager().initLoader(0, null, this); within Fragment's onActivityCreated

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

However, that yields the following problem : onLoadFinished will be called twice during configuration changes (Rotation)

We can simulate the problem as follow.

Code

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();
}

Logging

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

According to Android: LoaderCallbacks.OnLoadFinished called twice, one of the proposed solution is calling initLoader in 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);
}

Here is the logging. It looks OK now after we move initLoader to onResume.

Logging

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

I was wondering

Why the proposed solution work? Is this a bug? Should we file a bug to Google regarding this behavior? Maybe there is a ticket being filed to Google, but I cannot find it.

解决方案

Why the proposed solution is working

If we call getLoaderManager() in onActivityCreated() then we initialize variable Fragment.mLoaderManager.

As result we have mLoaderManager.doReportStart() call in Fragment.performStart() on 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();
    }
}

It is cause of first call of onLoadFinished.

Later in FragmentActivity.onStart() we have call to lm.finishRetain() (see code snippet):

 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();
         }
     }
 }

It is cause of second call of onLoadFinished.

OK. Now consider the case when we call getLoaderManager().initLoader(0, null, this) in onResume().

If we do it this way, we don't have nor mLoaderManager.doReportStart() nor lm.finishRetain() after onActivityCreated(), but we have onLoadFinished call during initLoader() instead:

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;
}

You can see info.callOnLoadFinished() call in this snippet:

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

I think it is clear :)