Android:LoaderCallbacks.OnLoadFinished调用两次

我注意到使用Android Loaders和Fragments的奇怪的情况。 当调用方向改变onLoadFinished后调用LoaderManager.initLoader()时,虽然没有调用(虽然文档build议我应该为此做好准备),但在此之后调用两次。 这里是链接到谷歌组中描述相同的情况下https://groups.google.com/forum/?fromgroups#!topic/android-developers/aA2vHYxSskU 。 我写了示例应用程序,我只在Fragment.onActivityCreated()中启动简单的Loader来检查是否发生这种情况。 有人注意到了吗?

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

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

这个问题通过一个CursorLoader返回一个已经closures的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); 

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

initLoader文档说,

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

我build议你在这个示例中实现类似onStartLoading函数的东西

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

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

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

任何方式,如果你附上一些代码,我会尽力给你更多的帮助。

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

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

这里loaderCallbacks实现了你通常的Loadercallback

 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中一样,它将获取/创build负载pipe理器,并将调用它。 为了避免这种情况,您需要稍后调用它,就像在onResume中一样。

onActivityCreated调用initLoader ,您可以检测旋转:

 @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 ,如果loader中的数据还没有可用,你仍然需要在initLoader后调用initLoader

希望有所帮助。

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

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

既然所有寻找这个主题都不可避免地结束了,我只想补充一下我的经验。 正如@jperera所说,罪魁祸首是LoaderManager将调用onLoadFinished(),如果加载器已经存在。 在我的情况下,我在FragmentPager中有碎片,并滚动2个标签,然后再次滚动它会导致我的旧片段开始创build自己。

由于在onCreate()中放置initLoader()也会导致双重callback,所以我把initLoader()放在了onResume()中。 但事件序列最终是onCreate(),因为loaders存在,所以LoaderManager调用callback,然后调用onResume(),触发另一个initLoader()和onLoadFinished()序列。 IE,另一个双重callback。

我find了一个“马特”的快速解决scheme。 所有的数据加载后(如果你有多个加载器),销毁所有的加载器,所以他们的callback不会被称为额外的时间。

我面对这个问题。但我曾经用destroyloader(YOUR_ID)装载的方法调用destroyloader(YOUR_ID) 。 那么加载器不会再次调用backgrdound任务两次。