当列表数据变更时,调用 notifyDataSetChanged() 是最省事的。无需关心变更的细节,一股脑统统刷一遍就完事了。但这样做也是最昂贵的。读完这一篇源码走查就知道为啥它这么昂贵了。
观察者模式
Adapter.notifyDataSetChanged()
将刷新操作委托给AdapterDataObservable
public class RecyclerView { public abstract static class Adapter<VH extends ViewHolder> { private final AdapterDataObservable mObservable = new AdapterDataObservable(); public final void notifyDataSetChanged() { mObservable.notifyChanged(); } } }
AdapterDataObservable
是RecyclerView
的静态内部类,它继承自Observable
:
public class RecyclerView { static class AdapterDataObservable extends Observable<AdapterDataObserver> { public void notifyChanged() { // 遍历所有观察者并委托之 for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } ... } }
Observable
是一个抽象的可被观察者:
// 被观察者, 泛型表示观察者的类型 public abstract class Observable<T> { // 观察者列表 protected final ArrayList<T> mObservers = new ArrayList<T>(); // 注册观察者 public void registerObserver(T observer) { ... synchronized(mObservers) { ... mObservers.add(observer); } } // 注销观察者 public void unregisterObserver(T observer) { ... synchronized(mObservers) { int index = mObservers.indexOf(observer); ... mObservers.remove(index); } } // 移除所有观察者 public void unregisterAll() { synchronized(mObservers) { mObservers.clear(); } } }
Observable
持有一组观察者,用泛型表示观察者的类型,且定义了注册和注销观察者的方法。
Adapter 数据的观察者是什么时候被注册的?
public class RecyclerView { // 列表数据变化的观察者实例 private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); // 为 RecyclerView 设置 Adapter private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { if (mAdapter != null) { // 移除之前的观察者 mAdapter.unregisterAdapterDataObserver(mObserver); mAdapter.onDetachedFromRecyclerView(this); } ... final Adapter oldAdapter = mAdapter; mAdapter = adapter; if (adapter != null) { // 注册新的观察者 adapter.registerAdapterDataObserver(mObserver); adapter.onAttachedToRecyclerView(this); } ... } public abstract static class Adapter<VH extends ViewHolder> { // 注册观察者 public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) { mObservable.registerObserver(observer); } } }
在为 RecyclerView 绑定 Adapter 的时候,一个观察者实例RecyclerViewDataObserver
被注册了:
public class RecyclerView { private class RecyclerViewDataObserver extends AdapterDataObserver { RecyclerViewDataObserver() {} @Override public void onChanged() { assertNotInLayoutOrScroll(null); mState.mStructureChanged = true; processDataSetCompletelyChanged(true);// 下节分析 if (!mAdapterHelper.hasPendingUpdates()) { requestLayout(); } } ... } }
它继承自一个抽象的观察者AdapterDataObserver
:
public class RecyclerView { public abstract static class AdapterDataObserver { public void onChanged() {} public void onItemRangeChanged(int positionStart, int itemCount) {} public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { onItemRangeChanged(positionStart, itemCount); } public void onItemRangeInserted(int positionStart, int itemCount) {} public void onItemRangeRemoved(int positionStart, int itemCount) {} public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {} } }
AdapterDataObserver 定义了 6 个更新列表的方法,其中第 1 个是全量更新,后面的 5 个都是局部更新。这一篇着重分析全量更新。
在分析具体更新逻辑之前,可以先做一个总结:
RecyclerView 使用观察者模式刷新自己,刷新即是通知所有的观察者。
观察者被抽象为
AdapterDataObserver
,它们维护在AdapterDataObservable
中。
在为 RecyclerView 绑定 Adapter 的同时,一个数据观察者实例被注册给 Adapter。
将一切都无效化
在真正地刷新列表之前,做了一些准备工作:
public class RecyclerView { void processDataSetCompletelyChanged(boolean dispatchItemsChanged) { mDispatchItemsChangedEvent |= dispatchItemsChanged; mDataSetHasChangedAfterLayout = true; // 将当前所有表项无效化 markKnownViewsInvalid(); } // 将当前所有表项无效化 void markKnownViewsInvalid() { // 遍历列表所有表项 final int childCount = mChildHelper.getUnfilteredChildCount(); for (int i = 0; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); // 列表中每个表项的 ViewHolder 添加 FLAG_UPDATE 和 FLAG_INVALID 标志位 if (holder != null && !holder.shouldIgnore()) { holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); } } markItemDecorInsetsDirty(); // 将缓存中表项无效化 mRecycler.markKnownViewsInvalid(); } }
RecyclerView 遍历了当前所有已经被加载的表项,并为其 ViewHolder 添加FLAG_UPDATE
和FLAG_INVALID
标志位。这些标志位会在即将到来的“布局表项”过程中决定是否要为表项绑定数据。(下一节分析)
除了将当前所有表项都无效化外,还调用了mRecycler.markKnownViewsInvalid()
:
public class RecyclerView { public final class Recycler { void markKnownViewsInvalid() { // 遍历所有离屏缓存 final int cachedCount = mCachedViews.size(); for (int i = 0; i < cachedCount; i++) { final ViewHolder holder = mCachedViews.get(i); // 将每个离屏缓存中的 ViewHolder 也添加 FLAG_UPDATE 和 FLAG_INVALID 标志位 if (holder != null) { holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); holder.addChangePayload(null); } } if (mAdapter == null || !mAdapter.hasStableIds()) { // 将离屏缓存中的 ViewHolder 存入缓存池 recycleAndClearCachedViews(); } } } }
RecyclerView 将所有离屏缓存中的 ViewHolder 也都做了无效化处理。还将它们回收到缓存池。(关于 RecyclerView 多级缓存的详细介绍可以点击RecyclerView 缓存机制 | 如何复用表项?)
至此,又可以做一个阶段性总结:
RecyclerView 在真正刷新列表之前,将一切都无效化了。包括当前所有被填充表项及离屏缓存中的 ViewHolder 实例。无效化体现在代码上即是为 ViewHolder 添加 FLAG_UPDATE 和 FLAG_INVALID 标志位。
真正的刷新
回看一下onChange()
中刷新列表的具体逻辑:
public class RecyclerView { private class RecyclerViewDataObserver extends AdapterDataObserver { RecyclerViewDataObserver() {} @Override public void onChanged() { assertNotInLayoutOrScroll(null); mState.mStructureChanged = true; // 将一切都无效化 processDataSetCompletelyChanged(true); if (!mAdapterHelper.hasPendingUpdates()) { // 真正的刷新 requestLayout(); } } ... } }
在将一切都无效化后,调用了View.requestLayout()
,即请求重新布局,该请求会不断地向父控件传递,一直传到 DecorView,DecorView 继续将请求传递给 ViewRootImpl,利用 Profiler 查看调用链如下图所示:(关于如何使用 Profiler 走查源码可以点击RecyclerView 的滚动是怎么实现的?(一)| 解锁阅读源码新姿势)
ViewRootImpl 收到重绘请求后调用scheduleTraversals()
来触发一次从根视图开始的重绘。重绘任务被包装成一个 Runnable 交由Choreographer
暂存。
Choreographer
紧接着订阅了下一个垂直同步信号。待下一个信号到来,它就会向主线程消息队列中发送一条消息,当主线程处理到这条消息时,从根视图开始的自顶向下重绘就启动了。(关于这其中的细节分析可以点击读源码长知识 | Android卡顿真的是因为”掉帧“?)
View.requestLayout()
会为控件添加两个标志位:
public class View { public void requestLayout() { ... // 添加两个标志位 mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; // 向父控件传递重绘请求 if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } ... } }
被添加了PFLAG_FORCE_LAYOUT
和PFLAG_INVALIDATED
标志位的控件,在重绘时会触发布局,即onLayout()
会被调用:
果然在 Profiler 的调用链中得到了证实,列表的重新布局意味着重新布局其中的每一个表项,体现在代码上即是LinearLayoutManager.onLayoutChildren()
。在RecyclerView 动画原理 | pre-layout,post-layout 与 scrap 缓存的关系中有提到过这个方法。
public class LinearLayoutManager { public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { ... // detach 并 scrap 表项 detachAndScrapAttachedViews(recycler); ... // 填充表项 fill() }
RecyclerView 在布局表项之前会先调用detachAndScrapAttachedViews(recycler)
清空现有表项,然后再填充新表项。
public class RecyclerView { public abstract static class LayoutManager { // 删除现存表项并回收它们 public void detachAndScrapAttachedViews(@NonNull Recycler recycler) { // 遍历现存表项并逐个回收它们 final int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { final View v = getChildAt(i); scrapOrRecycleView(recycler, i, v); } } // 回收表项 ViewHolder 实例 private void scrapOrRecycleView(Recycler recycler, int index, View view) { final ViewHolder viewHolder = getChildViewHolderInt(view); ... // 回收到缓存池 if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); } // 回收到 scrap 缓存 else { detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); } } } }
所有现存表项被逐个遍历,对应的 ViewHolder 实例被逐个回收。因为在重新布局之前表项都被添加了FLAG_INVALID
标志位,只要表项未被移除,它们都会被回收到缓存池 RecyclerViewPool 中。(从 Profiler 调用链中也得到了证实。)
回收现存表项之后,紧接着就调用了fill()
填充表项:
public class LinearLayoutManager { int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) { // 计算剩余空间 int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace; // 不停的往列表中填充表项,直到没有剩余空间 while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { // 填充单个表项 layoutChunk(recycler, state, layoutState, layoutChunkResult); ... } } // 填充单个表项 void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) { // 获取下一个被填充的视图 View view = layoutState.next(recycler); ... // 填充视图 addView(view); ... } }
填充表项是一个 while 循环,每次都调用layoutState.next()
获取下一个该被填充的表项:
public class LinearLayoutManager { static class LayoutState { View next(RecyclerView.Recycler recycler) { ... // 委托 Recycler 获取下一个该填充的表项 final View view = recycler.getViewForPosition(mCurrentPosition); ... return view; } } } public class RecyclerView { public final class Recycler { public View getViewForPosition(int position) { return getViewForPosition(position, false); } } View getViewForPosition(int position, boolean dryRun) { // 调用链最终传递到 tryGetViewHolderForPositionByDeadline() return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; } }
沿着调用链,就走到了一个复用表项的关键方法tryGetViewHolderForPositionByDeadline()
,方法中按优先级尝试着从不同缓存中获取 ViewHolder 实例。(关于该方法的详细介绍可以点击RecyclerView 缓存机制 | 如何复用表项?)
就这样刚才被存入缓存池的表项,又在这一个个地被命中了。
拿到 ViewHolder 实例后,就得判断是否需要为它绑定数据:
public class RecyclerView { public final class Recycler { // 从缓存获取 ViewHolder 实例并绑定数据 ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { ... if (mState.isPreLayout() && holder.isBound()) { ... } // 如果 ViewHolder 需要更新或者无效了, 则重新为其绑定数据 else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); // 绑定数据 bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); } ... } private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, int position, long deadlineNs) { ... // 绑定数据 mAdapter.bindViewHolder(holder, offsetPosition); ... } } public abstract static class Adapter<VH extends ViewHolder> { public final void bindViewHolder(@NonNull VH holder, int position) { ... // 熟悉的绑定数据回调 onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); ... } } public abstract static class ViewHolder { // 更新标志位 static final int FLAG_UPDATE = 1 << 1; // 判断 ViewHolder 是否需要被更新 boolean needsUpdate() { return (mFlags & FLAG_UPDATE) != 0; } } }
因为在上一节的“无效化”阶段,ViewHolder 被添加了 FLAG_UPDATE 和 FLAG_INVALID 标志位,所以就满足了!holder.isBound() || holder.needsUpdate() || holder.isInvalid()
这个条件,从缓存池命中的 ViewHolder 就得重新绑定数据。
总结
- RecyclerView 使用观察者模式刷新自己,刷新即是通知所有的观察者。
- 观察者被抽象为
AdapterDataObserver
,它们维护在AdapterDataObservable
中。
- 在为 RecyclerView 绑定 Adapter 的同时,一个数据观察者实例被注册给 Adapter。
- RecyclerView 在真正刷新列表之前,将一切都无效化了。包括当前所有被填充表项及离屏缓存中的 ViewHolder 实例。无效化体现在代码上即是为 ViewHolder 添加 FLAG_UPDATE 和 FLAG_INVALID 标志位。
RecyclerView.requestLayout()
是驱动列表刷新的源头。调用该方法后,会从根视图自顶向下地进行重绘。RecyclerView 的重绘表现为重新布局所有表项。
- RecyclerView 重新布局表项是这样进行的:先回收现存表项到缓存池,再重新填充它们。因为这些表项的 ViewHolder 实例在重绘之前都被“无效化”了,所以即使数据没变也逃不掉重新执行绑定数据的操作。
可见notifyDataSetChanged()
有多昂贵!
推荐阅读
RecyclerView 系列文章目录如下: