56

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

4

10 回答 10

39

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

    @Override
public void onResume()
{
    super.onResume();
    getLoaderManager().initLoader(0, null, this);
}
于 2013-01-25T14:40:34.720 回答
32

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

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

我猜这是一个错误或疏忽。虽然将 initLoader() 移动到 onResume 可能有效,但我能够做的是在完成后删除加载器:

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

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

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

  getLoaderManager().destroyLoader(MUSIC_LOADER_ID);

这似乎表现如预期,没有额外的调用。

于 2014-03-04T21:17:42.307 回答
6

initLoader 文档说,

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

我建议你在这个示例中实现类似 onStartLoading 函数

为了快速测试,您可以尝试:

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

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

无论如何,如果您附上一些代码,我会尽力为您提供更多帮助。

于 2012-12-17T21:41:13.953 回答
5

我解决了 onLoadFinished 像这样被调用两次的问题。在你的 Fragment.onActivityCreated() 像这样初始化你的加载器

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) {
        ...
        ...
    }
};
于 2016-03-06T07:10:22.127 回答
3

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

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

于 2014-05-05T22:18:36.093 回答
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如果在 loader 的loadInBackground. initLoader要解决此问题,如果来自加载程序的数据尚不可用,您仍需要在轮换后调用。

希望有帮助。

于 2015-02-04T10:39:25.340 回答
0

还可以比较 onLoadFinished(Loader loader, Object data) 中的数据对象。如果数据对象与您已有的数据对象匹配,则在调用 onLoadFinished 时您不能做任何事情。例如:

public void onLoadFinished(Loader loader, Object data) {
        if(data != null && mData != data){
            //Do something
        }
}
于 2015-02-16T01:58:42.623 回答
0

我遇到了这个问题。但是我习惯于调用destroyloader(YOUR_ID)in loaderfinished 方法。然后加载程序不会再次调用 backgrdound 任务两次。

于 2016-06-14T05:52:04.450 回答
0

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

于 2018-03-30T00:56:40.210 回答
0

由于所有对这个主题的搜索都不可避免地会在这里结束,我只是想补充一下我的经验。正如@jperera 所说,罪魁祸首是如果加载器已经存在,LoaderManager 将调用 onLoadFinished() 。就我而言,我在 FragmentPager 中有片段,并且滚动 2 个选项卡然后再次滚动到它旁边会导致我的旧片段开始自行创建。

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

解决方案

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

于 2016-02-16T21:38:19.877 回答