RecyclerView源码分析之二 滚动时候的ViewHolder的回收和复用

本文分析RecyclerView使用线性布局竖直滚动时ViewHolder的回收和复用。正常滚动只涉及Recycler.mCachedViews、Recycler.mRecyclerPool。滚动时,fill方法触发回收,从缓存取或创建View进行复用,从mCachedViews取的无需重新绑定,从RecycledViewPool取的需重新绑定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

源码版本:androidx1.3.2

分析场景:

RecyclerView 使用线性布局,方向为竖直方向,布局从上到下。第一次设置 LayoutManager 和 Adapter ,正常显示数据以后,滚动 RecyclerView。

先说下结论:

  1. 在正常滚动过程中,RecyclerView ViewHolder 的回收和复用只涉及 Recycler.mCachedViews、Recycler.mRecyclerPool。
  2. RecyclerView会回收滑出屏幕的View,当回收一个ViewHolder A 的时候,如果如果mCachedView缓存已达上限(默认是2),会调用 addViewHolderToRecycledViewPool 从mCachedViews中移除最老的ViewHolder,添加到RecyclerViewPool中(每种ItemViewType的缓存池大小默认是5)。然后把A加入到Recycler.mCachedViews。
  3. 从 Recycler.mCachedViews 返回的 ViewHolder 是不会重新 onBindViewHolder 的。
  4. 从 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) {
   
   
        <
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值