NotifyDataSetChanged- RecyclerView -它是异步调用吗?

16

我正在尝试跟踪在 notifyDataSetChanged 在 recyclerview 上执行后的一组语句。但是当我调试我的应用程序时,调试器会在我的 notifyDataSetChanged 之后到达几行代码,然后再进入recyclerview的适配器的 onBindViewHolder。因此,我的问题是:notifyDataSetChanged 是异步调用吗?如果是,我们是否会得到回调? PS:我已经搜索了这个答案,但是找不到合适的答案,所以我向社区寻求帮助。


调试器有时可能会表现得很奇怪。您能否先放置一些日志并进行检查? - Ruchira Randana
@RuchiraRandana 我甚至重新启动了我的笔记本电脑,但问题仍然存在 :( - Vaibhav Sharma
所以,你确认日志也按照你先前所述的顺序打印出来了。对吗? - Ruchira Randana
@RuchiraRandana 是的,日志显示这是异步调用。 - Vaibhav Sharma
你需要从主线程调用 notifyDatasetChanged(),该方法会在主线程上进行更改。因此它不能是异步的。 - Droidekas
3个回答

35

我是RecyclerView的作者,

当你调用notifyDataSetChanged时,RecyclerView会使数据失效,但直到下一帧动画才会更新UI。这就是android视图系统的工作方式。当小部件无效(例如更改其数据)时,它会请求布局,这意味着它将在下一个视图遍历中重新测量和重新布局。这样做是为了将所有更改批处理,直到下一次屏幕更新为止。这就是为什么notifyDataSetChanged不会立即触发onBind的原因。

所以是的,你可以将这称为异步调用,但这并不意味着你可以在多线程上运行它(这两个概念完全不同)。你仍然必须在主线程上对适配器进行所有更改。当你更改适配器时,你必须立即通知RecyclerView,这就是为什么通知也必须在主线程上的原因。

这种限制的原因是,如果在布局过程中更改了数据集,那么对于布局管理器来恢复到稳定状态非常困难(例如想象RecyclerView在同时调用onBind(5)和另一个线程删除第5个项)。此外,考虑这些更改将需要大量的同步,这将是一个巨大的性能惩罚而没有任何好处。这就是为什么所有UI组件都是单线程的原因。


notify* 是异步的,这是否意味着相对于 scrollToPosition 它发生的顺序也是不确定的?我目前遇到了一些奇怪的问题,似乎表明存在竞态条件... http://stackoverflow.com/questions/39868846/recyclerview-race-condition-between-notify-and-scrolltopostion - david.mihola
@yigit 你能看一下这个问题吗?https://twitter.com/theapache64/status/1323628468677054466 - theapache64

2
TLDR:不行。您只能从UI线程中调用notifyDatasetChanged(),如此处所示,该方法会更改UI,这些更改只能在主线程上完成。这使得调用同步。
这个调用的目的是使用更改的元素重新绘制UI,就像在视图上调用requestFocus()一样。
文档中可以看到:

当使用此方法时,RecyclerView将尝试为报告具有稳定ID的适配器合成可见的结构更改事件。这可以帮助进行动画和视觉对象持久性,但是仍然需要重新绑定和重新布局单个项目视图。

由于视图被重绘,因为数据集期望发生更改,它必须在主UI线程上进行(只有主循环程序(即UI线程)才能修改视图)。
您可以拥有多个AdapterObservers,它们可以在ui上执行您需要的操作。
另外,从文档中可以看到:
  • 此事件未指定数据集发生了什么更改,因此强制所有观察者假定所有现有项和结构可能不再有效。LayoutManager将被强制完全重新绑定并重新布局所有可见视图。

默认观察器会认为所有数据都已更改,因为从Recycler view源代码中可以看出。
 public final void notifyDataSetChanged() {
        mObservable.notifyChanged();
    }

这是一个同步调用,应该避免并作为最后的选择使用。

0
我遇到了这个问题,因为我需要根据我的RecyclerView的大小进行一些布局重新配置,而且由于高度设置为wrap_content,每当数据发生变化时,大小就会改变。为了有效地响应布局更改,您可以使用您的布局管理器的一个简短子类(在我的情况下是LinearLayoutManager),并覆盖onLayoutCompleted方法:
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()){
        @Override
        public void onLayoutCompleted (RecyclerView.State state){
            super.onLayoutCompleted(state);
            // update UI for new layout
        }
    });

在我的情况下,我需要在 RecyclerView 大小更改后滚动 ScrollView,而这段代码起了作用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接