【问题标题】:Android: LoaderCallbacks.OnLoadFinished called twiceAndroid:LoaderCallbacks.OnLoadFinished 调用了两次
【发布时间】:2012-07-02 12:10:27
【问题描述】:

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

【问题讨论】:

  • 我遇到了这个问题,但在我的情况下,我发现是从其他地方调用 notifyDataSetChanged() 导致光标被更新并被调用 onLoadFinished()

标签: android loader


【解决方案1】:

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

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

【讨论】:

  • 为我工作。这是一个错误吗?我们应该将此错误提交给 Google 吗?
  • @Andrew 你能解释一下为什么会这样吗?即使initLoader(...)onCreate() 中,onLoadFinished() 也会被调用。顺便说一句,很好发现!
  • 我不知道...我在加载程序框架中发现了很多愚蠢的错误,我什至无法开始描述...好吧,也许不是错误,但有些实现是一点也不容易集成。
  • 这看起来不是一个好的解决方法。假设您从 onResume initLoader 中的片段开始另一个活动...恢复此片段将再次调用 initloader。
【解决方案2】:

这个问题通过 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);

这似乎符合预期,没有额外的调用。

【讨论】:

  • destroyLoader() 是最可靠的修复方法,尤其是在处理必须在onCreateonResume 生命周期之外启动的加载程序时。
  • destroyLoader() 也在破坏游标 我可以破坏加载器但保留游标吗??
【解决方案3】:

initLoader documentation 说,

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

我建议你在 sample 上实现类似 onStartLoading 的功能

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

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

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

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

【讨论】:

    【解决方案4】:

    我解决了 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) {
            ...
            ...
        }
    };
    

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

        当从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

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

        希望对您有所帮助。

        【讨论】:

          【解决方案7】:

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

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

          【讨论】:

            【解决方案8】:

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

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

            解决方案

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

            【讨论】:

              【解决方案9】:

              我遇到过这个问题。但是我以前在 loaderfinished 方法中调用 destroyloader(YOUR_ID)。那么加载器不会再次调用backgrdound任务两次。

              【讨论】:

                【解决方案10】:

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

                【讨论】:

                  猜你喜欢
                  • 2017-06-25
                  • 2013-01-21
                  • 2012-09-08
                  • 2016-04-27
                  • 2019-02-27
                  • 2019-12-04
                  • 2018-09-15
                  • 2012-10-10
                  • 1970-01-01
                  相关资源
                  最近更新 更多