源码版本:androidx1.3.2
分析场景:
RecyclerView 使用线性布局,方向为竖直方向,布局从上到下。第一次设置 LayoutManager 和 Adapter ,正常显示数据以后,滚动 RecyclerView。
先说下结论:
- 在正常滚动过程中,RecyclerView ViewHolder 的回收和复用只涉及 Recycler.mCachedViews、Recycler.mRecyclerPool。
- RecyclerView会回收滑出屏幕的View,当回收一个ViewHolder A 的时候,如果如果mCachedView缓存已达上限(默认是2),会调用 addViewHolderToRecycledViewPool 从mCachedViews中移除最老的ViewHolder,添加到RecyclerViewPool中(每种ItemViewType的缓存池大小默认是5)。然后把A加入到Recycler.mCachedViews。
- 从 Recycler.mCachedViews 返回的 ViewHolder 是不会重新 onBindViewHolder 的。
- 从 RecyclerViewPool 返回的 ViewHolder 是会重新 onBindViewHolder 的。
其他注意的点: 在调试过程中发现,GapWorker 的 prefetchPositionWithDeadline 方法干扰到 RecyclerView 的回收和复用过程。有时候会导致 导致 Recycler.mCachedViews的 size变成了3(默认是2)。这个不是主要流程,我们先忽略,有兴趣可以仔细研究研究。
RecyclerView的缓存的 ViewHolder 的地方有 Recycler.mAttachedScrap、Recycler.mChangedScrap、Recycler.mCachedViews、Recycler.mRecyclerPool。
缓存 View 的地方有 Recycler.mViewCacheExtension、ChildHelper.mHiddenViews。 如下所示:
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
//...
}
class ChildHelper {
private final List<View> mHiddenViews = new ArrayList<View>();
//...
}
本篇文章只分析在滚动的时候的回收和复用。只涉及 Recycler.mCachedViews、Recycler.mRecyclerPool 。在后续的文章中,会进行一个总结说明每一个缓存的作用。
在滚动时候的回收
在RecyclerView滚动的时候,会调用 LinearLayoutManager 的fill方法。fill方法内部会发生View的回收和复用。
/**
* @param recycler 当前关联到RecyclerView的recycler。
* @param layoutState 该如何填充可用空间的配置信息。
* @param state Context passed by the RecyclerView to control scroll steps.
* @param stopOnFocusable 如果为true的话,遇到第一个可获取焦点的View则停止填充。
* @return 返回添加的像素。
*/
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
final int start = layoutState.mAvailable;
//...
//注释0处,如果layoutState.mScrollingOffset不为SCROLLING_OFFSET_NaN的话,调用recycleByLayoutState方法,从这个方法名,我们可以看出来,这是一个回收View的方法。
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
//...
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
//注释1处,获取并添加子View,然后测量、布局子View并将分割线考虑在内。
layoutChunk(recycler, state, layoutState, layoutChunkResult);
//条件满足的话,跳出循环
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
//注释2处,这里也会判断是否要调用recycleByLayoutState方法。
//在滚动的时候,会把滑出屏幕的View回收。
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
return start - layoutState.mAvailable;
}
注释0处和注释2处,调用 recycleByLayoutState 方法,从这个方法名可以看出来,这是一个回收View的方法。接下来我们看看其中的细节。
LinearLayoutManager 的 recycleByLayoutState 方法。
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
int scrollingOffset = layoutState.mScrollingOffset;
int noRecycleSpace = layoutState.mNoRecycleSpace;
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
//注释1处
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
} else {
//注释2处
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
}
}
注释1处,以默认竖直方向的LinearLayoutManager来说,就是手指从上向下滑动的时候,回收从下面滑出屏幕的View。
注释2处,以默认竖直方向的LinearLayoutManager来说,就是手指从下向上滑动的时候,回收从上面滑出屏幕的View。
LinearLayoutManager的recycleViewsFromEnd方法。
private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
final int childCount = getChildCount();
if (scrollingOffset < 0) {
return;
}
final int limit = mOrientationHelper.getEnd() - scrollingOffset + noRecycleSpace;
if (mShouldReverseLayout) {
//...
} else {
//从后向前遍历
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedStart(child) < limit
|| mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
//注释1处,遇到第一个View的top坐标小于limit就停止。然后调用recycleChildren方法,回收从childCount - 1到i之间的所有View。
recycleChildren(recycler, childCount - 1, i);
return;
}
}
}
}
注释1处,遇到第一个View的top坐标小于limit就停止。然后调用recycleChildren方法,回收从 [childCount - 1,i) 左闭右开之间的所有View。
LinearLayoutManager的recycleViewsFromStart方法。
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
if (scrollingOffset < 0) {
return;
}
// ignore padding, ViewGroup may not clip children.
final int limit = scrollingOffset - noRecycleSpace;
final int childCount = getChildCount();
if (mShouldReverseLayout) {
//...
} else {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
//注释1处,遇到第一个View的bottom坐标大于limit就停止。然后调用recycleChildren方法,回收从0到i之间的所有View。
recycleChildren(recycler, 0, i);
return;
}
}
}
}
注释1处,遇到第一个View的bottom坐标大于limit就停止。然后调用recycleChildren方法,回收从 [0,i) 左闭右开之间的所有View。
接下来我们看看recycleChildren方法。
LinearLayoutManager的recycleChildren方法。
/**
* 回收指定索引之间的子View。
*
* @param startIndex 包括
* @param endIndex 不包括
*/
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
//startIndex == endIndex的话,直接return。比如滚动距离很短,没有View需要回收。
if (startIndex == endIndex) {
<