我注意到使用 Android 加载器和片段的奇怪情况。当我在方向更改后调用 LoaderManager.initLoader() 时,不会调用 onLoadFinished (尽管文档建议我应该为此做好准备),但在此之后调用了两次。这是在描述相同情况的谷歌群组中发布的链接https://groups.google.com/forum/?fromgroups#!topic/android-developers/aA2vHYxSskU。我编写了示例应用程序,其中我只在 Fragment.onActivityCreated() 中初始化了简单的加载程序,以检查这种情况是否发生并且确实发生了。有人注意到这个吗?
10 回答
您可以将 initLoader() 方法放在 Fragment 的 onResume() 回调中;那么 Loader 的 onLoadFinished() 将不再被调用两次。
@Override
public void onResume()
{
super.onResume();
getLoaderManager().initLoader(0, null, this);
}
这个问题对我来说很明显,因为 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);
这似乎表现如预期,没有额外的调用。
如果在调用点调用者处于启动状态,并且请求的加载器已经存在并生成了它的数据,则回调 onLoadFinished(Loader, D)
我建议你在这个示例中实现类似 onStartLoading 函数
为了快速测试,您可以尝试:
@Override protected void onStartLoading() {
forceLoad();
}
此启动 loadInBackground 函数,然后在 Fragment 中启动 onLoadFinished。
无论如何,如果您附上一些代码,我会尽力为您提供更多帮助。
我解决了 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) {
...
...
}
};
问题是它调用了两次:
1.来自 Fragment.onStart
2.来自 FragmentActivity.onStart
唯一的区别是在 Fragment.onStart 中它检查 mLoaderManager 是否为 null !这意味着如果您在 onStart 之前调用 getLoadManager,就像在 onActivityCreated 中一样,它将获取/创建负载管理器并被调用。为避免这种情况,您需要稍后调用它,例如在 onResume 中。
当你打电话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
要解决此问题,如果来自加载程序的数据尚不可用,您仍需要在轮换后调用。
希望有帮助。
还可以比较 onLoadFinished(Loader loader, Object data) 中的数据对象。如果数据对象与您已有的数据对象匹配,则在调用 onLoadFinished 时您不能做任何事情。例如:
public void onLoadFinished(Loader loader, Object data) {
if(data != null && mData != data){
//Do something
}
}
我遇到了这个问题。但是我习惯于调用destroyloader(YOUR_ID)
in loaderfinished 方法。然后加载程序不会再次调用 backgrdound 任务两次。
如果实现 AppCompatActivity,请仔细检查您是否在所有情况下都使用 getSupportLoaderManager()(destroyLoader/initLoader 等)。我错误地将 getSupportLoaderManager() 与 getLoaderManager() 一起使用并遇到了同样的问题。
由于所有对这个主题的搜索都不可避免地会在这里结束,我只是想补充一下我的经验。正如@jperera 所说,罪魁祸首是如果加载器已经存在,LoaderManager 将调用 onLoadFinished() 。就我而言,我在 FragmentPager 中有片段,并且滚动 2 个选项卡然后再次滚动到它旁边会导致我的旧片段开始自行创建。
由于将 initLoader() 放在 onCreate() 中也会导致双重回调,因此我将 initLoader() 放在 onResume() 中。但是事件序列最终是 onCreate(),由于加载器存在,LoaderManager 调用回调,然后调用 onResume(),触发另一个 initLoader() 和 onLoadFinished() 序列。IE,另一个双重回调。
解决方案
我通过"Matt"找到了一个快速解决方案。加载所有数据后(如果您有多个加载器),销毁所有加载器,这样它们的回调就不会被调用额外的时间。