图解RecyclerView缓存机制

ViewGroupRecyclerViewScrollingViewRecyclerArrayList<ViewHolder> mAttachedScrapArrayList<ViewHolder> mChangedScrapArrayList<ViewHolder> mCachedViewsRecycledViewPool mRecyclerPoolRecycledViewPool

删除View缓存使用图解

RecyclerView移除元素

在垂直布局的RecyclerView中展示了Item0~Item4这5个TextView,我们现在把Item2删掉。数据更新后,我们调用Adapter#notifyItemRemoved(2)来提交更新。

RecyclerView删除Item

dispatchLayoutStep1阶段:

  1. 调用detachViewFromParent把Item0~Item4这五个View从RecyclerView中detache掉,然后放入把ViewHolder放入mAttachedScrap进行缓存。ViewHodler都会被标记为FLAG_TMP_DETACHED,并且Item2会被标记为FLAG_REMOVED;
  2. 然后layout进行填充,把Item0~Item4从mAttachedScrap中取出,然后调用attachViewToParent重新加入到RecyclerView中。
  3. 调用onCreateViewHolder创建一个新ViewHolder(Item5),执行onBindViewHolder,并addView到RecyclerView中。

dispatchLayoutStep2阶段:

  1. 调用detachViewFromParent把Item0~Item5这六个View从RecyclerView中detache掉,然后把ViewHolder放入mAttachedScrap进行缓存。ViewHolder都会被标记为FLAG_TMP_DETACHED,并且Item2会被标记为FLAG_REMOVED;
  2. 除了Item2的ViewHolder之外,把其他ViewHolder从mAttachedScrap缓存中取出,然后调用attachViewToParent重新加入到RecyclerView中。

dispatchLayoutStep3阶段:

  1. 从mAttachedScrap取出Item2,然后把Item2 View添加到mHiddenViews中;
  2. 在Item2 ItemAnimator的onAnimationFinished事件中把Item2从mAttachedScrap中移除,添加到RecycledViewPool中。

修改View缓存使用图解

RecyclerView修改Item

RecyclerView中包含Item0~Item4共五个TextView,我们把Item2的文案更新为”New 2”。通过调用Adapter#notifyItemChanged(2)来通知RecyclerView进行更新。

RecyclerView修改

插入View缓存使用图解

RecyclerView插入Item

RecyclerView插入Item时序

预加载缓存 mCachedViews

预加载的view会放在这里,这个数组最大默认是2。首次绘制出来,如果没有滑动屏幕,mCachedViews为空,当滑动UI后会将预加载的一个,以及划出的加入缓存中。

mCachedViews存储元素

如果是向下滑动,则屏幕下面的元素会缓存两个,比如上图右边向下把H滑出屏幕,而C会露出,则mCachedViews中保存的是[B, H, I]。

滑动时预加载流程

androidx.recyclerview.widget.RecyclerView#onTouchEvent接收到MotionEvent.ACTION_MOVE事件时,如果判断滑动超过一定的距离时,就任务用户自滑动操作。然后就会调用GapWorker#postFromTraversal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//androidx/recyclerview/widget/GapWorker.java
void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
if (recyclerView.isAttachedToWindow()) {
if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) {
throw new IllegalStateException("attempting to post unregistered view!");
}
if (mPostTimeNs == 0) {
mPostTimeNs = recyclerView.getNanoTime();
recyclerView.post(this); //将自己转到主线程去执行
}
}
//设置用户滑动的距离
recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
}
GapWorkerGapWorkerLayoutPrefetchRegistryImplLayoutPrefetchRegistryImplLinearLayoutManagerLinearLayoutManagerRecyclerRecyclerrunprefetchbuildTaskListcollectPrefetchPositionsFromViewmCount 清零collectAdjacentPrefetchPositions预加载临近元素获取下一个元素的positionaddPosition预加载的position保存到mPrefetchArray数组中mCount 加一return根据mCount数量添加预加载任务到mTasks列表中flushTasksWithDeadlineflushTaskWithDeadlineprefetchPositionWithDeadlinetryGetViewHolderForPositionByDeadline

mAttachedScrap

先看下源码执行流程:

AdapterAdapterRecyclerViewDataObserverRecyclerViewDataObserverAdapterHelperAdapterHelperRecyclerViewRecyclerViewViewViewnotifyItemRemoved(2)onItemRangeRemoved(2,1)onItemRangeRemoved(2,1)添加一个UpdateOp指令到mPendingUpdates列表中triggerUpdateProcessor()requestLayout()super.requestLayout()

从上面时序图可以看出,最终调用到了父类View#requestLayout(),然后调用mParent.requestLayout();,沿着view层级一直调用到android.view.ViewRootImpl#requestLayout()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//android-31\android\view\ViewRootImpl.java
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//添加同步屏障,保障vsync信号会优先执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//等待下一个vsync信号来执行TraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}

performTraversals();

if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}

从上面可以看出,requestLayout不会立马执行,而是等待下一个vsync信号到来再执行,最终执行android.view.ViewRootImpl#performTraversals,这个函数很长有800多行,这里就不贴代码了,这里面就会View的绘制的measure、layout、draw三大流程。分别对应:ViewRootImpl#measureHierarchyViewRootImpl#performLayoutViewRootImpl#performDraw

RecyclerViewRecyclerViewLinerLayoutManagerLinerLayoutManagerRecyclerView.LayoutManagerRecyclerView.LayoutManagerRecyclerRecycleronLayoutdispatchLayoutdispatchLayoutStep1onLayoutChildrendetachAndScrapAttachedViews倒序遍历RecyclerView子ViewViewHolder设置FLAG_TMP_DETACHEDdetachViewFromParent()scrapView(view)ViewHolder添加到mAttachedScrap列表中fill循环从mAttachedScrap取出ViewHouderlayoutChunkgetViewForPosition从mAttachedScrap取出。如果获取不到将会调用onCreateViewHolder()并bindaddViewunscrapView()ViewHolder从mAttachedScrap中删除mAttachedScrap.remove(holder)attachViewToParent(对于onCreateViewHolder创建的新holder将会调用addView,而不是attache)

从缓存获取ViewHolder

首次创建ViewHolder,以及从缓存中获取都是通过androidx.recyclerview.widget.RecyclerView.Recycler#getViewForPosition(int),这个方法会优先使用缓存中ViewHolder,如果找不到,就会调用createViewHolder和bindViewHolder创建一个新的,并返回ViewHodler的itemView:

ViewHolder查找创建流程

ItemAnimator

RecyclerViewRecyclerViewViewViewChoreographerChoreographerItemAnimatorItemAnimatordispatchLayoutStep3postAnimationRunnerpostOnAnimationpostCallback(CALLBACK_ANIMATION)runPendingAnimationscallback中调用