RecyclerView Adapter 调用 notifyDataSetChanged后执行流程

因为遇到了一个问题调用notifyDataSetChanged不刷新界面,但是滑动一下屏幕,界面就刷新了。
在网上找了很久也没有解决。所以,自己追下流程看下,当我们调用notifyDataSetChanged时,它是怎么更新数据和刷新界面的呢?

先说下我在网上找到的解决方案吧
1)notifyDataSetChanged不是在主线程中执行的;
2)更新数据源时,更新的不是Adapter绑定的数据源。

好了,进入正题,来看下RecyclerView Adapter 的notifyDataSetChanged是怎么执行的吧

我们来看下Adapter的notifyDataSetChanged 方法

android adapter notifyDataSetChanged有时无效 notifydatasetchanged()_数据集


可以看到它调用的mObservable的notifyChanged(),mObservable是什么东西?它的定义如下,命名看起来跟观察者模式有关

android adapter notifyDataSetChanged有时无效 notifydatasetchanged()_数据集_02


继续看,在这里注册了一个观察者

android adapter notifyDataSetChanged有时无效 notifydatasetchanged()_数据集_03


android adapter notifyDataSetChanged有时无效 notifydatasetchanged()_观察者模式_04

可以确定是观察者模式了,观察者来自这里

android adapter notifyDataSetChanged有时无效 notifydatasetchanged()_观察者模式_05


继续看,notifyChanged后发生了啥

android adapter notifyDataSetChanged有时无效 notifydatasetchanged()_数据集_06

调用了观察者的onChanged方法,我们来看RecyclerViewDataObserver的onChanged方法

android adapter notifyDataSetChanged有时无效 notifydatasetchanged()_回调函数_07


我们可以看到首先调用了第一行,检查RecyclerView是否在正在计算布局或滚动,是就并抛出一个IllegalStateException

第二行,设置了一个状态,告诉recycleView数据集已改变。官方介绍是:

自上次调用onLayoutChildren以来数据集的结构是否已更改

第三行,processDataSetCompletelyChanged(true);,

android adapter notifyDataSetChanged有时无效 notifydatasetchanged()_回调函数_08


官方解释:

据我们所知,处理的数据集已完全更改。一旦布局发生,所有附加项目都应丢弃或动画化。附加项目被标记为无效。因为仍可能在“数据集”之间预取项目 完全更改”事件和布局事件,所有缓存的项目都将被丢弃。

我的理解是,在通知RecyclerView数据集更改了,在布局更新之后丢弃掉之前的数据集。

看1.2两行,设置了标识位;

第三行,调用如下函数:

android adapter notifyDataSetChanged有时无效 notifydatasetchanged()_观察者模式_09


这个方法是:

将所有已知的视图标记为无效。 用于回应“整个世界可能已经改变”数据更改事件。

继续下去也是在通知视图无效和数据集改变。

我们继续回到onChanged方法:

android adapter notifyDataSetChanged有时无效 notifydatasetchanged()_回调函数_10


进行这个mAdapterHelper.hasPendingUpdates()函数的判断,再调用requestLayout()进行界面刷新。

看到这里,我怀疑他是mAdapterHelper.hasPendingUpdates()为true,没有进行requestLayout().

我们去看hasPendingUpdates

boolean hasPendingUpdates() {
    return mPendingUpdates.size() > 0;
}

//mPendingUpdates 定义
final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();

android adapter notifyDataSetChanged有时无效 notifydatasetchanged()_回调函数_11


可以看到返回的是false,是会进入的requstLayout的。所以这里是没有问题的,继续往下走发现会回到断点位置notifyDataSetChanged。然后,会进入View,但是就没法走了,不是走到的正常的代码。

大概可能是看出来requstLayout并不是立即就会更新代码,而是要过一段时间才会更新。

但是,为什么过了一段时间却没有更新,得滚动一下它才会更新呢?
目前没有找到原因,不行就只能让他滚一个像素更新了。

总结:
问题没解决,倒是知道了RecyclerView,是怎么更新界面的。观察者模式在Android中真是无处不在啊。

PS:
以下几个方法,是类似的,都是通过观察者模式调用到RecyclerViewDataObserver 里面相应的change方法进行界面刷新

* @see #notifyItemChanged(int)
* @see #notifyItemInserted(int)
* @see #notifyItemRemoved(int)
* @see #notifyItemRangeChanged(int, int)
* @see #notifyItemRangeInserted(int, int)
* @see #notifyItemRangeRemoved(int, int)

PS1:我来解决方法啦:
(先说下大概的背景,我的页面上方有一个网络播放器(WebView),下方是一个播放列表(RecyclerView ),中间是正在播放的视频信息)
而我要做的是当我点击RecyclerView中的条目时,播放它并改变条目颜色,表明当前条目正在播放
而问题是,我点击了之后,会播放相应条目,但条目颜色不更新。
我们开始一直以为是RecyclerView的更新问题,还特地看了源码,追了界面更新流程,都没有什么问题,但就是不刷新界面。
在同事的指导下,发现将显示视频播放时间的Chronometer去掉后,能正常更新,虽然不知道为啥,但是,总算能行了吧?
然后,现实并非如此。不播放的时候是正常,但一开始播放视频就出问题。
在经过我的不断尝试之后,总算是发现了问题,只要网络播放器通过JavaScript调用了activity中的回调,并且更新了activity的界面,就不能刷新RecyclerView的条目了。
想不明白为啥,然后,突然想到在activity中的回调更新界面时,加入了runOnUiThread包裹。如下,

//回调函数
//...
runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //ui更新
            }
});
//...后续操作

然后,完美解决。
打了回调函数的线程是在JavaBridge。
大致查了下资料,JavaBridge是跟
不加runOnUiThread他也是可以正常UI更新的,但是,就是影响到了RecyclerView的更新,实在不明白,但是,困扰好几天的问题好歹解决了。