Android:LoaderCallbacks.OnLoadFinished两次调用

时间:2012-07-02 12:10:27

标签: android loader

我注意到使用Android Loaders和Fragments的奇怪情况。当我调用方向更改onLoadFinished后调用LoaderManager.initLoader()时(尽管文档建议我应该为此做好准备),但在此之后调用它两次。以下是链接到Google群组中的帖子,其中描述了相同的情况https://groups.google.com/forum/?fromgroups#!topic/android-developers/aA2vHYxSskU。我编写了示例应用程序,其中我只在Fragment.onActivityCreated()中初始化简单的Loader以检查是否发生了这种情况。有人注意到了吗?

10 个答案:

答案 0 :(得分:37)

您可以将initLoader()方法放在Fragment的onResume()回调中;那么Loader的onLoadFinished()将不再被调用两次。

    @Override
public void onResume()
{
    super.onResume();
    getLoaderManager().initLoader(0, null, this);
}

答案 1 :(得分:25)

这个问题对我来说很明显,CursorLoader返回一个已经关闭的Cursor:

android.database.StaleDataException: Attempted to access a cursor after it has been closed.

我猜这是一个错误或疏忽。将initLoader()移动到onResume可能会起作用,我能做的就是在完成后删除Loader:

启动加载器(在我的onCreate中):

  getLoaderManager().initLoader(MUSIC_LOADER_ID, null, this);

然后我完成它(基本上在onLoadFinished结束时)

  getLoaderManager().destroyLoader(MUSIC_LOADER_ID);

这似乎表现得如预期,没有额外的电话。

答案 2 :(得分:6)

initLoader documentation说,

  

如果在呼叫点处,呼叫者处于其启动状态,并且   请求的加载器已存在并已生成其数据   回调onLoadFinished(Loader,D)

我建议你在这个sample

上实现onStartLoading函数

如需快速测试,您可以尝试:

@Override protected void onStartLoading() {
    forceLoad();
}

这个启动loadInBackground函数,然后在Fragment中onLoadFinished。

无论如何,如果你附上一些代码,我会尽量给你更多的帮助。

答案 3 :(得分:5)

我解决了像这样被调用两次的onLoadFinished的问题。 在Fragment.onActivityCreated()中,像你这样启动你的Loader

if (getLoaderManager().getLoader(LOADER_ID) == null) {
    getLoaderManager().initLoader(LOADER_ID, bundle, loaderCallbacks);
} else {
    getLoaderManager().restartLoader(LOADER_ID, bundle, loaderCallbacks);

}

这里loaderCallbacks实现了你通常的Loader回调

private LoaderManager.LoaderCallbacks<T> loaderCallbacks
        = new LoaderManager.LoaderCallbacks<T>() {
    @Override
    public Loader<T> onCreateLoader(int id, Bundle args) {
        ...
        ...
    }

    @Override
    public void onLoadFinished(Loader<T> loader, T data) {
        ...
        ...
    }

    @Override
    public void onLoaderReset(Loader<T> loader) {
        ...
        ...
    }
};

答案 4 :(得分:3)

问题是它叫了两次:
1.来自Fragment.onStart
2.来自FragmentActivity.onStart

唯一的区别是在Fragment.onStart中它会检查mLoaderManager!= null。 这意味着如果你在onStart之前调用getLoadManager,就像在onActivityCreated中一样,它将获取/创建load manager并调用它。为避免这种情况,您需要稍后调用它,例如onResume。

答案 5 :(得分:2)

initLoader致电onActivityCreated时,您可以检测到轮换:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    if (savedInstanceState == null) {
        // fresh new fragment, not orientation/config change
        getLoaderManager().initLoader(YOUR_LOADER_ID, null, mCallbacks);
    }
    ...
}

这样加载程序就会按预期运行,从而导致单onLoadFinished次调用。
它不再被调用,所以如果你想要加载器的数据,你可以将它保存在你的片段中,例如通过覆盖onSaveInstanceState

编辑:
我刚刚意识到如果在加载器onLoadFinished期间发生旋转,则不会调用loadInBackground。要解决此问题,如果来自加载程序的数据尚未可用,您仍需要在轮换后调用initLoader

希望有所帮助。

答案 6 :(得分:0)

您还可以比较onLoadFinished(Loader加载器,对象数据)中的数据对象。如果数据对象与您已有的数据对象匹配,则在调用onLoadFinished时您无法执行任何操作。例如:

public void onLoadFinished(Loader loader, Object data) {
        if(data != null && mData != data){
            //Do something
        }
}

答案 7 :(得分:0)

由于所有搜索此主题的内容都不可避免地会在这里结束,我只想添加我的经验。正如@jperera所说,罪魁祸首是如果加载器已经存在,LoaderManager将调用onLoadFinished()。在我的情况下,我在FragmentPager中有片段并滚动2个标签然后再次滚动它会导致我的旧片段开始创建自己。

由于在onCreate()中放置initLoader()也会导致双重回调,因此我将initLoader()放在onResume()中。但是事件序列最终是onCreate(),LoaderManager调用回调,因为存在加载器,然后调用onResume(),触发另一个initLoader()和onLoadFinished()序列。 IE,另一个双回调。

溶液

我找到了"Matt"的快速解决方案。加载完所有数据后(如果你有多个加载器),销毁所有加载器,这样它们的回调就不会被称为额外的时间。

答案 8 :(得分:0)

我遇到过这个问题。但我曾经在装载完成的方法中调用destroyloader(YOUR_ID)。然后加载器不再两次调用backgrdound任务。

答案 9 :(得分:0)

如果实现AppCompatActivity,请仔细检查您是否在所有情况下使用getSupportLoaderManager()(destroyLoader / initLoader等)。我错误地将getSupportLoaderManager()与getLoaderManager()一起使用并遇到了同样的问题。