RecyclerView源码解析

RecyclerView渲染流程和获取缓存

RecyclerView也是一个View,也要遵循View的三个步骤。onMeasureonLayoutonDraw.

那么就来照着这个顺序来解剖RecyclerView。

解剖依照的项目是google的android sample

onLayout

RecyclerView中,子View都是在LayoutManger中进行计算大小,布局的。所以其实RecyclerViewonLayout()方法只是一个空壳。这里就可以简化RecyclerViewonLayout()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class RecyclerView {
void dispatchLayout() {
//...
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
// 在RecyclerView中,最重要的就是调用下面这个方法。
dispatchLayoutStep2();
}
// ...
// step3中间有主要的流程,是关于缓存的,但是我们还没有走到缓存
// 所以我们就不看了
dispatchLayoutStep3();
}
}
1
2
3
4
5
6
7
8
9
10
11
class RecyclerView {
private void dispatchLayoutStep2() {
//...
// 之后就进入最关键的onLayoutChildren。
// 也就是从这里开始,layout移交给LayoutManager处理。
mLayout.onLayoutChildren(mRecycler, mState);
// ...
}
}
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
class LinearLayoutManager {
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
// 通过检查子项和其他变量,找到锚点坐标和锚点项目位置
// 2) fill towards start, stacking from bottom
// 从下往上填充,从底部开始堆
// 3) fill towards end, stacking from top
// 从上往下填充,从顶部开始堆
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
// 滚动以满足从底部堆栈的要求。创建布局状态
// 看到这里我们其实有
if (mAnchorInfo.mLayoutFromEnd) {
//...
} else {
// 这里有两个fill(),这里要说明一下。
// 前面省略的代码有一个锚的概念.
// 它通过一系列的计算得出自己要的锚在屏幕哪个位置.
// 然后再在从锚点开始,从锚点到顶,从锚点到底进行layout和draw
// 这里我们进入fill方法分析一下。
fill(recycler, mLayoutState, state, false);
// ...
fill(recycler, mLayoutState, state, false);
// ...
}
// ...
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class RecyclerView {
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
// ...
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
// 得到的结果就是拿到了adapter的view,
// 并加入到了RecyclerView,并且已经做了measure和layout
// 需要进入这个方法进行分析。
layoutChunk(recycler, state, layoutState, layoutChunkResult);
// ...
}
// ...
return start - layoutState.mAvailable;
}
}
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
class LinearLayoutManager {
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
// 这个next()方法就是获取RecyclerView的缓存的ViewHolder的中 // 的View的方法.
// 缓存机制分为三层,
// 第一层通过CacheView把控,存在这里的ViewHolder不会调用onCreateViewHolder和onBindViewHolder方法。
// 第二层通过ViewCacheExtension实现,这层缓存通过用户自定义实现,但是这里有个很奇怪的事情是我并不知道该如何给RecyclerView,因为RecyclerView缓存ViewHolder的时候并没有一个接口导出来。
// 第三层通过RecyclerPool实现,RecyclerPool用viewType来存储ViewHolder。
// 这里就是获取缓存的重中之重了。那么来分析这个方法
View view = layoutState.next(recycler);
// ...
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
// 这里addView做了两个操作
// 将这个view放入ChildHelper的bucket中
// 将view加入到RecyclerView中
addView(view);
} else {
}
} else {
// ...
}
// 后面有一系列的操作,各种计算,都是为了measuer和layout我们得
// 到的ViewHolder的view。
}
}
1
2
3
4
5
6
7
8
9
10
11
class LinearLayoutManager.LayoutState {
View next(RecyclerView.Recycler recycler) {
//...
// 进入getViewForPosition()方法
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
}
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Recycler {
public View getViewForPosition(int position) {
// 直接进入getViewForPosition()方法。
return getViewForPosition(position, false);
}
// 这个方法就是我们真正获取或者创建ViewHolder的地方了。
// 这里的逻辑和之前描述缓存的意思相同,
// 先通过CacheView、再通过ViewCacheExtension、最后通过RecyclerPool方法。如果三个缓存里都没有的话,那只能重新创建了。
View getViewForPosition(int position, boolean dryRun) {
//...
ViewHolder holder = null;
//...
// 1) Find from scrap by position
if (holder == null) {
// 这里我简单对逻辑进行解释。
// 这里通过position来查找缓存
// 1.在mAttachedScrap列表查找ViewHolder。
// 2.在mHiddenViews列表中查找View。但是我还没有找到这个缓存是什么个情况。
// 3.在mCachedViews列表中查找ViewHolder。这里可以真正的称之为第一层缓存。
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
}
if (holder == null) {
// ...
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
// 这里通过adapter的itemId来在mAttachedScrap中查抄ViewHolder。
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
}
// 这里就是用户自定义的缓存方法了,他只是调用了外面的接口,而接口返回的view就是要我们来提供了。说不定以后我们会学习一下怎么来设置自定义缓存
if (holder == null && mViewCacheExtension != null) {
// ...
}
if (holder == null) {
// 这里通过viewType去RecyclerViewPool来找相同的viewType的ViewHolder
holder = getRecycledViewPool().getRecycledView(type);
}
if (holder == null) {
// 走到这里说明已经没有缓存了,直接重新构建 // ViewHolder的生命周期了
holder = mAdapter.createViewHolder(RecyclerView.this, type);
//...
}
}
//...
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// ...
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
// ...
// 这里最重要的就是走了adapter的bindViewHolder方法,
// 在CacheView不会进入这个逻辑,且不会调用bindViewHolder方法,而其他缓存出来的ViewHolder会进入这个方法。
mAdapter.bindViewHolder(holder, offsetPosition);
// ...
}
// 这里的操作已经无伤大雅了。略过
return holder.itemView;
}
}
onDraw

RecyclerViewonDraw()方法主要是用来渲染ItemDecoration(),而onDraw()方法里会调用onDrawChild(),对每个ViewHolder进行渲染,所以没啥好解释的。

小结一下:到这里我们总结了关于RecyclerView的初始化流程和获取缓存ViewHolder的流程。

RecyclerView自己做的事情其实是比较少的,对于Layout流程他差不多都是给到LayoutManager来执行,LayoutManger执行每个Item的onLayout方法。

RecyclerView的缓存是交给Recycler来管理,获取缓存的流程是通过CacheView、ViewCacheExtension、RecyclerPool来执行的。

CacheView的ViewHolder完全保持Item之前的状态,既不会走onCreateViewHolder也不会走onBindViewHolder。CacheView的默认最大容量是两个,可以自定义修改。

ViewCacheExtension暂时不知道怎么使用,网上也没有啥例子可以参考。

RecyclerPool是通过ViewType来进行管理的,多个RecyclerView可以共用一个RecyclerPool,当CacheView放不下的时候,栈底的元素就会放到RecyclerPool中。

RecyclerView滑动和放入缓存

上面的初始化和显示分析了获取缓存的流程和逻辑,而只是获取缓存,没有加入缓存的话那么这个分析也是没有意义的。

RecyclerView的的滑动就是加入缓存的一个入口,当页面上有Item被划出屏幕的时候,那么这个Item的ViewHolder就加入到缓存了。

触发滑动事件大部分是用户用手机滑动屏幕进行,所以滑动的入口就是在监听滑动事件的onTouchEvent的ACTION_MOVE事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class RecyclerView {
public boolean onTouchEvent(MotionEvent e) {
switch (action) {
case MotionEvent.ACTION_MOVE: {
// 这个是scroll事件的内部实现
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
}
break;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
class RecyclerView {
boolean scrollByInternal(int x, int y, MotionEvent ev) {
if (mAdapter != null) {
if (y != 0) {
// 因为我们的设定是竖直方向进行滑动,所以是进入这个逻辑
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
}
}
}
}

​ scrollVerticallyBy这个方法也是走的LayoutManager,所以说,LayoutManager还承接了滑动的真实逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
class LinearLayoutManager {
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
return scrollBy(dy, recycler, state);
}
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
}
}

我们又走到了fill方法,而fill方法就是保证新滑动出来的item能够被填充到RecyclerView中。因为我们之前已经在fill方法中分析过获取缓存的方法,这里分析fill方法我们把重点放到将Item放入缓存的流程。

1
2
3
4
5
6
7
8
class LinearLayoutManager {
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
// 这里就是回收Item的入口
recycleByLayoutState(recycler, layoutState);
}
}
}
1
2
3
4
5
6
class LinearLayoutManager {
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
// 这个方法是从上开始回收Item。
recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
}
}
1
2
3
4
5
6
7
8
9
10
11
class LinearLayoutManager {
} else {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedEnd(child) > limit || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
recycleChildren(recycler, 0, i);
return;
}
}
}
}

关于找出要回收的Item的逻辑比较有意思。

1.png

所以他是通过滑动距离和每个item的底部的坐标来判断item的上一个item是否划出了屏幕。

下面来看看是怎么回收Item。

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
46
47
48
49
50
51
class LinearLayoutManager {
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
if (startIndex == endIndex) {
return;
}
if (endIndex > startIndex) {
for (int i = endIndex - 1; i >= startIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
}
}
public void removeAndRecycleViewAt(int index, Recycler recycler) {
// 获取这个ViewHolder的view。
final View view = getChildAt(index);
// 在页面上删除这个已经移出的view。
removeViewAt(index);
// 将view交给Recycler,Recycler来完成回收的动作
recycler.recycleView(view);
}
public void recycleView(View view) {
recycleViewHolderInternal(holder);
}
void recycleViewHolderInternal(ViewHolder holder) {
if (forceRecycle || holder.isRecyclable()) {
if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE)) {
int cachedViewSize = mCachedViews.size();
// 当cachedView的数量大于等于最大缓存数的时候,那么把最开始加入到CacheView的Item移出,把新的Item放进CacheView。然后把移出的Item放入到RecyclerPool中。
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize --;
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
// 这里我们再看看RecyclerPool是如何存储被回收的Item的。
if (!cached) {
addViewHolderToRecycledViewPool(holder);
recycled = true;
}
}
}
void addViewHolderToRecycledViewPool(ViewHolder holder) {
getRecycledViewPool().putRecycledView(holder);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class RecycledViewPool {
public void putRecycledView(ViewHolder scrap) {
// 首先拿到这个ViewHolder的viewType。
final int viewType = scrap.getItemViewType();
// 通过这个viewType拿到相对应的存储的列表,简单介绍里面的逻辑,从一个map中拿到这个list,如果没有个这个kv值,那么重新创建一个list并放入到这个map,并且创建一个这个viewType的最大值放入到另一个map中。最大值默认为5.
final ArrayList scrapHeap = getScrapHeapForType(viewType);
// 如果RecyclerPool中这个viewType的数量已经达到最大值,那么直接不存储了。
if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
return;
}
// 如果RecyclerPool中这个viewType的数量小于最大值,那么存入RecyclerPool中。
scrap.resetInternal();
scrapHeap.add(scrap);
}
}

至此,关于RecyclerView的缓存流程已经完成,这里我们总结一下,

1.RecyclerView分为三级缓存,cacheView,自定义缓存和RecyclerPool。

2.CacheView默认最大为两个,会保存Item的状态。(不会去调用onBindViewHolder)

3.RecyclerPool根据viewType来管理存储,不会保存Item的状态(会去调用onBindViewHolder)

4.自定义缓存只有让客户端程序员自己定义,并没有在回收Item的时候对其进行操作。