剖析RecyclerView的回收与复用

深入剖析RecyclerView的ViewHolder复用与回收机制,了解其内部工作流程,包括缓存分级、复用流程、回收流程及缓存策略。掌握RecyclerView在滚动过程中如何高效管理视图资源。

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

同学们面试中有没有经常被问到RecyclerView的回收复用逻辑呢?是否又跟我一样一脸懵逼?今天我们就从源码的角度入手,扒开RecyclerView的“神秘面纱”,让面试官都见鬼去吧!

回收复用

何谓回收与复用呢?RecyclerView在滚动时,会将滑出屏幕外的View进行一个缓存,这就是ViewHolder的回收,从屏幕外面滑进来的ViewHolder呢,会从缓存中取ViewHolder,如果有缓存没有,那么就进行创建,如果找到了就进行复用。所以准确的应该说是ViewHolder的回收与复用。

ViewHolder

RecyclerView中每一条Item就会被封装成一个ViewHolder对象,我们一般通过ViewHolder进行view控件的初始化逻辑

复用

我们先来看复用流程,从哪里入手呢?RecyclerView回收与复用是在手指滑动的时候进行的,所以可以从RecyclerView的事件函数中来进行解剖,滑动事件,必然是 MotionEvent.ACTION_MOVE这个分支了。

@Override
public boolean onTouchEvent(MotionEvent e) {
    // ................................
    switch (action) {
    
        case MotionEvent.ACTION_MOVE: {
            // ..............................................

            final int x = (int) (e.getX(index) + 0.5f);
            final int y = (int) (e.getY(index) + 0.5f);
            int dx = mLastTouchX - x;
            int dy = mLastTouchY - y;
			// 根据横向滑动和纵向滑动,设置状态为拖动状态
            if (mScrollState != SCROLL_STATE_DRAGGING) {
                boolean startScroll = false;
                // 横向滑动
                if (canScrollHorizontally) {
                    if (dx > 0) {
                        dx = Math.max(0, dx - mTouchSlop);
                    } else {
                        dx = Math.min(0, dx + mTouchSlop);
                    }
                    if (dx != 0) {
                        startScroll = true;
                    }
                }
                // 纵向滑动
                if (canScrollVertically) {
                    if (dy > 0) {
                        dy = Math.max(0, dy - mTouchSlop);
                    } else {
                        dy = Math.min(0, dy + mTouchSlop);
                    }
                    if (dy != 0) {
                        startScroll = true;
                    }
                }
                if (startScroll) {
                    setScrollState(SCROLL_STATE_DRAGGING);
                }
            }

            // 如果状态是拖动状态
            if (mScrollState == SCROLL_STATE_DRAGGING) {
                mReusableIntPair[0] = 0;
                mReusableIntPair[1] = 0;
                // 嵌套滑动处理,这里不需要关心
                if (dispatchNestedPreScroll(
                        canScrollHorizontally ? dx : 0,
                        canScrollVertically ? dy : 0,
                        mReusableIntPair, mScrollOffset, TYPE_TOUCH
                )) {
                    dx -= mReusableIntPair[0];
                    dy -= mReusableIntPair[1];
                    // Updated the nested offsets
                    mNestedOffsets[0] += mScrollOffset[0];
                    mNestedOffsets[1] += mScrollOffset[1];
                    // Scroll has initiated, prevent parents from intercepting
                    getParent().requestDisallowInterceptTouchEvent(true);
                }

                mLastTouchX = x - mScrollOffset[0];
                mLastTouchY = y - mScrollOffset[1];
				// 重点看这里
                if (scrollByInternal(
                        canScrollHorizontally ? dx : 0,
                        canScrollVertically ? dy : 0,
                        e, TYPE_TOUCH)) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                if (mGapWorker != null && (dx != 0 || dy != 0)) {
                    mGapWorker.postFromTraversal(this, dx, dy);
                }
            }
        }
        break;

        
    }

    // ...........................

    return true;
}

当手指在屏幕上触发任意事件时,RecyclerView先是处理了一些嵌套滑动的事件,不过这并不是今天的重点,主要看这句:

if (scrollByInternal(
                        canScrollHorizontally ? dx : 0,
                        canScrollVertically ? dy : 0,
                        e, TYPE_TOUCH)) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
    // ......................................
    if (mAdapter != null) {
        mReusableIntPair[0] = 0;
        mReusableIntPair[1] = 0;
        scrollStep(x, y, mReusableIntPair);
        // 这里省略了嵌套滑动的一些参数处理。。。。。。。
    }
    // ........................................
    return consumedNestedScroll || consumedX != 0 || consumedY != 0;
}

代码很多,这里把无关的东西都省略了,我们发现scrollByInternal方法中调用了scrollStep()

void scrollStep(int dx, int dy, @Nullable int[] consumed) {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();

    TraceCompat.beginSection(TRACE_SCROLL_TAG);
    fillRemainingScrollValues(mState);

    int consumedX = 0;
    int consumedY = 0;
    if (dx != 0) {
        consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
    }
    if (dy != 0) {
        consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
    }

    TraceCompat.endSection();
    repositionShadowingViews();

    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);

    if (consumed != null) {
        consumed[0] = consumedX;
        consumed[1] = consumedY;
    }
}

LayoutManager

可以看到,根据横向滑动还是纵向滑动,分别调用了mLayout.scrollHorizontallyBy(dx, mRecycler, mState)mLayout.scrollVerticallyBy(dy, mRecycler, mState),回收复用逻辑都是一样的,我们来看看纵向滑动:

mLayout是什么?

LayoutManager mLayout;

原来它就是初始化RecyclerView时设置的LayoutManager

我们拿LinearLayoutManager举例:

// LinearLayoutManager.java
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
        RecyclerView.State state) {
    if (mOrientation == HORIZONTAL) {
        return 0;
    }
    return scrollBy(dy, recycler, state);
}

继续跟踪scrollBy方法:

int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    // .............................
    final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
    final int absDy = Math.abs(dy);
    updateLayoutState(layoutDirection, absDy, true, state);
    // 关键点fill()
    final int consumed = mLayoutState.mScrollingOffset
            + fill(recycler, mLayoutState, state, false);
    // ....................
    return scrolled;
}

fill()

发现它调用了fill()方法,顾名思义,它是用来填充View的,这应该就是复用逻辑了,点进去证明一下:

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    // ............................................................................
    // 如果当前的position小于条目总数时,进入循环
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        if (RecyclerView.VERBOSE_TRACING) {
            TraceCompat.beginSection("LLM LayoutChunk");
        }
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        // ............................................................................
        // 以下是回收(缓存)逻辑
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
    }
    return start - layoutState.mAvailable;
}

在一个while循环中调用了layoutChunk(recycler, state, layoutState, layoutChunkResult);方法,这个方法实际上就是在获取缓存的View,然后进行复用,并展示;因为屏幕上的数据可能会展示多条目,所以用来while循环进行多个条目的展示。我们来看一下:

layoutChunk()

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    // layoutState.next(), 在recyclerview对象中查找		
    View view = layoutState.next(recycler);
    
    // 下面逻辑为获取到view之后,设置view参数,并进行展示,到此复用逻辑就走完了
    if (view == null) {
            if (DEBUG && layoutState.mScrapList == null) {
                throw new RuntimeException("received null view when unexpected");
            }
            // if we are laying out views in scrap, this may return null which means there is
            // no more items to layout.
            result.mFinished = true;
            return;
        }
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        }
    // 。。。。。。。。。。。。。。。。。。。。。。。。
}

可以看到,这个方法中通过layoutState.next(recycler)获取到view之后进行展示,复用逻辑就到这里结束了,我们重点来追踪next方法:

layoutState.next(recycler)

View next(RecyclerView.Recycler recycler) {
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

mScrapList这里先不看,view是通过recycler.getViewForPosition(mCurrentPosition);获取的。

tryGetViewHolderForPositionByDeadline

// RecyclerView
public View getViewForPosition(int position) {
    return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            // 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
            ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                // 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
            }
            // 1) Find by position from scrap/hidden list/cache
            if (holder == null) {
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                // 。。。。。。。。。。。。。。。。。。。。。。
            }
            if (holder == null) {
                // 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
                final int type = mAdapter.getItemViewType(offsetPosition);
                // 2) Find from scrap/cache via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                }
                // mViewCacheExtension是开发者自定义的缓存方案,一般用不到
                if (holder == null && mViewCacheExtension != null) {
                    
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        // 。。。。。。。。。。。。。。。。。。。。。
                    }
                }
                if (holder == null) { // fallback to pool
                	// 从缓存池中获取
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                if (holder == null) {
                    
                    // 实在没找到,直接调用adapter的createViewHolder方法创建
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    // 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
                }
            }
    
    		boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // do not update unless we absolutely have to.
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                if (DEBUG && holder.isRemoved()) {
                    throw new IllegalStateException("Removed holder should be bound and it should"
                            + " come here only in pre-layout. Holder: " + holder
                            + exceptionLabel());
                }
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                
                // tryBindViewHolderByDeadline方法中调用了adatper.bindViewHolder(),有兴趣的可以看看。
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }

            // 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
			// viewholder创建完成之后,设置view的参数,并将holder返回
            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if (!checkLayoutParams(lp)) {
                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else {
                rvLayoutParams = (LayoutParams) lp;
            }
            rvLayoutParams.mViewHolder = holder;
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
            return holder;
        }

缓存分级

这里可以看出,是先从缓存获取的ViewHolder, 然后直接使用ViewHolder的成员变量itemView进行返回。根据上面代码我们总结一下,获取ViewHolder分为以下几步:

(涉及到的方法大家可以自行点进去看一下,我这里直接写出来)

1. mChangedScrap

getChangedScrapViewForPosition(),我们可以把它看作第一级缓存

  • 从mChangedScrap中根据position去找,找到了直接返回
  • 如果根据position没有找到,那么就根据itemId来找,找到了直接返回,没找到返回null
ViewHolder getChangedScrapViewForPosition(int position) {
    // If pre-layout, check the changed scrap for an exact match.
    final int changedScrapSize;
    // 如果mChangedScrap为空,直接返回null
    if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
        return null;
    }
    // 从mChangedScra中根据position去找,找到了直接返回
    for (int i = 0; i < changedScrapSize; i++) {
        final ViewHolder holder = mChangedScrap.get(i);
        if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
        }
    }
    // 如果根据position没有找到,那么就根据itemId来找,找到了直接返回,没找到返回null
    if (mAdapter.hasStableIds()) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
            final long id = mAdapter.getItemId(offsetPosition);
            // 遍历changedScrapSize,如果找到了直接返回
            for (int i = 0; i < changedScrapSize; i++) {
                final ViewHolder holder = mChangedScrap.get(i);
                if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    return holder;
                }
            }
        }
    }
    return null;
}
2. mAttachedScrap & mCachedViews

如果mChangedScrap没有获取到,那么走到getScrapOrHiddenOrCachedHolderForPosition()方法,这是第二级缓存。

  • 试图从mAttachedScrap中通过position寻找viewholder,找到了直接返回
  • 否则从mCachedViews中查找,找不到还是返回null。
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
    final int scrapCount = mAttachedScrap.size();

    // 是试图从mAttachedScrap中通过position寻找viewholder,找到了直接返回
    for (int i = 0; i < scrapCount; i++) {
        final ViewHolder holder = mAttachedScrap.get(i);
        if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
                && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
        }
    }

    // 。。。。。。。。省略一些代码
    // 从mCachedViews中通过position寻找viewholder,找到了直接返回,找不到返回null
    final int cacheSize = mCachedViews.size();
    for (int i = 0; i < cacheSize; i++) {
        final ViewHolder holder = mCachedViews.get(i);
        // invalid view holders may be in cache if adapter has stable ids as they can be
        // retrieved via getScrapOrCachedViewForId
        if (!holder.isInvalid() && holder.getLayoutPosition() == position
                && !holder.isAttachedToTransitionOverlay()) {
            if (!dryRun) {
                mCachedViews.remove(i);
            }
         
            return holder;
       }
    }
    return null;
}
  • 如果mAttachedScrapmCachedViews也没有找到呢,继续从这两个里面找,不过这次是通过id查找了。往下看还有个getScrapOrCachedViewForId()方法,所以同学们注意,这还是属于第二级缓存哦~

    • 试图从mAttachedScrap中通过id寻找viewholder,找到了直接返回
    • 否则从mCachedViews中查找,找不到还是返回null。
    ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
        // Look in our attached views first
        final int count = mAttachedScrap.size();
        // 通过itemid,在mAttachedScrap查找
        for (int i = count - 1; i >= 0; i--) {
            final ViewHolder holder = mAttachedScrap.get(i);
            if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
                if (type == holder.getItemViewType()) {
                    //。。。。。。。。。。。。。。。。。。。。。。
                    return holder;
                }
                // 。。。。。。。。。
            }
        }
    
        // 通过itemid,在mCachedViews查找
        final int cacheSize = mCachedViews.size();
        for (int i = cacheSize - 1; i >= 0; i--) {
            final ViewHolder holder = mCachedViews.get(i);
            if (holder.getItemId() == id) {
                if (type == holder.getItemViewType()) {
                    if (!dryRun) {
                        mCachedViews.remove(i);
                    }
                    return holder;
                }
                // 。。。。。。。。。。。。。。。。
            }
        }
        return null;
    }
    
3. mViewCacheExtension

如果还是没有找到,就走到了mViewCacheExtension.getViewForPositionAndType(this, position, type);方法,mViewCacheExtension是开发这自定的一个缓存机制,可以通过recyclerview的方法去设置:

void setViewCacheExtension(ViewCacheExtension extension) {
    mViewCacheExtension = extension;
}

不过一般我们是用不到的啦

4. mRecyclerPool

这时候就走到了ViewHolder的缓存池 getRecycledViewPool().getRecycledView(type);

@Nullable
public ViewHolder getRecycledView(int viewType) {
    final ScrapData scrapData = mScrap.get(viewType);
    if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
        final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
        return scrapHeap.remove(scrapHeap.size() - 1);
    }
    return null;
}

可以看到,缓存池是获取到了最后一个ViewHolder,然后把获取到的这个从池中移除,所以这个缓存池是一个栈结构,先进后出。

找不到,直接创建

如果所有缓存中都没有找到,那么直接回调到了mAdapter.createViewHolder(RecyclerView.this, type),是不是就很熟悉了

至此,RecyclerView的复用逻辑就走完了,那么回收是怎么进行的呢?

回收(缓存)

正常流程

ViewHolder的回收也可以看作是进行缓存,因为它其实是将ViewHolder分别保存在了几个List集合中。

还记不记得View复用时走到了fill方法,View回收也是从这里出发的=

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
   
        // 省略了复用逻辑............................................................................
        // 以下是回收(缓存)逻辑
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
    }
    return start - layoutState.mAvailable;
}
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) {
        recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
    } else {
        recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
    }
}

回收时,调用到了recycleByLayoutState方法,里面有两个关键方法recycleViewsFromEnd & recycleViewsFromStart。 这里可以理解成上滑和下滑,移出屏幕的部分进行缓存。两个方法的回收逻辑都是一样的,我们随便看一个就可以了。

start为手指向上滑动时的回收

end为手指向下滑动时的回收

recycleViewsFromStart()

private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
        int noRecycleSpace) {
    // 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
    // ignore padding, ViewGroup may not clip children.
    final int limit = scrollingOffset - noRecycleSpace;
    final int childCount = getChildCount();
    if (mShouldReverseLayout) {
        for (int i = childCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedEnd(child) > limit
                    || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                // stop here
                recycleChildren(recycler, childCount - 1, i);
                return;
            }
        }
    } else {
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedEnd(child) > limit
                    || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                // stop here
                recycleChildren(recycler, 0, i);
                return;
            }
        }
    }
}

这里无论如何它都调用了recycleChildren方法

recycleChildren()

private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
    if (startIndex == endIndex) {
        return;
    }
    if (DEBUG) {
        Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
    }
    if (endIndex > startIndex) {
        for (int i = endIndex - 1; i >= startIndex; i--) {
            removeAndRecycleViewAt(i, recycler);
        }
    } else {
        for (int i = startIndex; i > endIndex; i--) {
            removeAndRecycleViewAt(i, recycler);
        }
    }
}

removeAndRecycleViewAt()

// RecyclerView.java
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
    final View view = getChildAt(index);
    removeViewAt(index);
    recycler.recycleView(view);
}

先从ViewGroup中移除了这个view, 然后通过recycleView(view)方法进行回收

recycleView(view)

public void recycleView(@NonNull View view) {
    // 1. 根据View获取ViewHolder
    ViewHolder holder = getChildViewHolderInt(view);
    if (holder.isTmpDetached()) {
        removeDetachedView(view, false);
    }
    // 2. 回收holder
    recycleViewHolderInternal(holder);
    // 结束item动画效果
    if (mItemAnimator != null && !holder.isRecyclable()) {
        mItemAnimator.endAnimation(holder);
    }
}

recycleViewHolderInternal(holder)

void recycleViewHolderInternal(ViewHolder holder) {
    // 进行一系列的判断。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
    if (forceRecycle || holder.isRecyclable()) {
        if (mViewCacheMax > 0
                && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                | ViewHolder.FLAG_REMOVED
                | ViewHolder.FLAG_UPDATE
                | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
            // Retire oldest cached view
            
            // 1. 首先是缓存到了mCachedViews中
            // 注意这里有个小逻辑,cachedViewSize = DEFAULT_CACHE_SIZE = 2.
            // 也就是说,如果缓存前,mCachedViews中有了2个View了,那么就先移除掉第一个View,将它移入缓存池中
            // 看下面的recycleCachedViewAt(0);方法
            int cachedViewSize = mCachedViews.size();
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                recycleCachedViewAt(0);
                cachedViewSize--;
            }

            int targetCacheIndex = cachedViewSize;
            if (ALLOW_THREAD_GAP_WORK
                    && cachedViewSize > 0
                    && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                // when adding the view, skip past most recently prefetched views
                int cacheIndex = cachedViewSize - 1;
                while (cacheIndex >= 0) {
                    int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                    if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                        break;
                    }
                    cacheIndex--;
                }
                targetCacheIndex = cacheIndex + 1;
            }
            // 2. mCachedViews默认最多放入2条数据,如果有2条了,那么就先移除第一条,然后再add到后面。
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
        // 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
    }
    //。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}

recycleCachedViewAt()

view进行缓存时,这里传入了0。看下面的代码,就是移除了mCachedViews中的数据,并将其put到了缓存池。

void recycleCachedViewAt(int cachedViewIndex) {
    if (DEBUG) {
        Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
    }
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    if (DEBUG) {
        Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
    }
    addViewHolderToRecycledViewPool(viewHolder, true);
    mCachedViews.remove(cachedViewIndex);
}
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
    clearNestedRecyclerViewIfNotNested(holder);
    View itemView = holder.itemView;
    // 。。。。。。。。。。。。。。。。。。。。。。。。
    // 分发一些回收事件
    if (dispatchRecycled) {
        dispatchViewRecycled(holder);
    }
    holder.mOwnerRecyclerView = null;
    // 将holder添加到缓存池
    getRecycledViewPool().putRecycledView(holder);
}

putRecycledView()

public void putRecycledView(ViewHolder scrap) {
    // 1. 先获取当前holder的viewType。
    final int viewType = scrap.getItemViewType();
    // 2. 获取缓存池中相同viewType的数量
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
    // 3. mMaxScrap = DEFAULT_MAX_SCRAP = 5. 
    // 也就是说,如果缓存池中相同viewType的数量超过了5条,那么就直接将此holder抛弃,不进行缓存。
    if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
        return;
    }
    if (DEBUG && scrapHeap.contains(scrap)) {
        throw new IllegalArgumentException("this scrap item already exists");
    }
    // 4. 将holder中的数据全部清空,以便后面的复用
    scrap.resetInternal();
    // 5. 将holder放入mScrapHeap中,也就是缓冲池
    scrapHeap.add(scrap);
}

根据上面的逻辑我们可以看到,缓存池中,相同的ViewType最多缓存5条数据。那么总结一下, mCacheViews和Pool是怎么工作的呢?

首先会判断mCacheViews的数量,如果超过2条,那么先移除第1条数据,让后将holder存入到里面,并将移除的这条数据放入缓存池中;同时,缓存池会判断当前holder的ViewType,如果相同的ViewType数量超过了5条,那么直接将此holder抛弃,不进行处理,否则才会将holder存入到池中,所以它跟CacheViews相反,是一个先进后出的结构

到这,recycleViewHolderInternal()方法的流程已经执行完了,不对啊,mChangedScrap和mAttachedScrap 去哪了呢?不要急

onLayout

我们从复用逻辑可以看到,获取到view之后通过addView添加的RecyclerView中了,这时必然会调用到onLayout布局的方法中,我们来看一下:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    dispatchLayout();
    TraceCompat.endSection();
    mFirstLayoutComplete = true;
}
void dispatchLayout() {
    // 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
    mState.mIsMeasuring = false;
    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
            || mLayout.getHeight() != getHeight()) {
        // First 2 steps are done in onMeasure but looks like we have to run again due to
        // changed size.
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else {
        // always make sure we sync them (to ensure mode is exact)
        mLayout.setExactMeasureSpecsFrom(this);
    }
    dispatchLayoutStep3();
}

可以看到用到了分发layout,dispatchLayoutStep1,dispatchLayoutStep2,dispatchLayoutStep3,3个方法,无论哪个方法,都势必会调用到mLayout.onLayoutChildren(mRecycler, mState);

onLayoutChildren

// LinearLayoutManager
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 (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
        // 如果总条目为0 ,移除所有view
        if (state.getItemCount() == 0) {
            removeAndRecycleAllViews(recycler);
            return;
        }
    }
   
   	// 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    // 1. 回收逻辑
    detachAndScrapAttachedViews(recycler);
    mLayoutState.mInfinite = resolveIsInfinite();
    mLayoutState.mIsPreLayout = state.isPreLayout();
    if (mAnchorInfo.mLayoutFromEnd) {
        // fill towards start
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtra = extraForStart;
        // 2. 复用view
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;
        final int firstElement = mLayoutState.mCurrentPosition;
        if (mLayoutState.mAvailable > 0) {
            extraForEnd += mLayoutState.mAvailable;
        }
        // fill towards end
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtra = extraForEnd;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        // 2. 复用view
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;

        if (mLayoutState.mAvailable > 0) {
            // end could not consume all. add more items towards start
            extraForStart = mLayoutState.mAvailable;
            updateLayoutStateToFillStart(firstElement, startOffset);
            mLayoutState.mExtra = extraForStart;
            // 2. 复用view
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
        }
    } else {
        // fill towards end
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtra = extraForEnd;
        // 2. 复用view
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;
        final int lastElement = mLayoutState.mCurrentPosition;
        if (mLayoutState.mAvailable > 0) {
            extraForStart += mLayoutState.mAvailable;
        }
        // fill towards start
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtra = extraForStart;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        // 2. 复用view
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;

        if (mLayoutState.mAvailable > 0) {
            extraForEnd = mLayoutState.mAvailable;
            // start could not consume all it should. add more items towards end
            updateLayoutStateToFillEnd(lastElement, endOffset);
            mLayoutState.mExtra = extraForEnd;
            // 2. 复用view
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
        }
    }

    // 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}

发现它通过detachAndScrapAttachedViews方法调用了scrapOrRecycleView

detachAndScrapAttachedViews(recycler);

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);
    }
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.shouldIgnore()) {
        if (DEBUG) {
            Log.d(TAG, "ignoring view " + viewHolder);
        }
        return;
    }
    // 如果当前viewHolder没有失效并且没有被移除,也就是说不是通过删除此条目,而是滑出了屏幕的情况下
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        removeViewAt(index);
        // 这里调用了recycleViewHolderInternal,上面正常流程的缓存逻辑
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        // 否则调用到scrapView
        detachViewAt(index);
        recycler.scrapView(view);
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}

我们现在主要看recycler.scrapView(view);

scrapView()

void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
            || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
            throw new IllegalArgumentException("Called scrap view with an invalid view."
                    + " Invalid views cannot be reused from scrap, they should rebound from"
                    + " recycler pool." + exceptionLabel());
        }
        holder.setScrapContainer(this, false);
        mAttachedScrap.add(holder);
    } else {
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
        mChangedScrap.add(holder);
    }
}

到这里清晰了吧,mAttachedScrap.add(holder);和mChangedScrap.add(holder); 。 所以只有当条目被移除之后,才会缓存到这两个里面。

fill()

当缓存完成之后,onLayoutChildren(*)方法继续调用了fill进行复用并展示在屏幕上。

流程图

只看源码有些枯燥,抛个时序图给大家吧

回收流程图

在这里插入图片描述

复用流程

在这里插入图片描述

留疑

通过回收逻辑我们知道,如果相同的ViewType超过5条时,那么ViewHolder不会缓存,复用不成立就会创建。所以当GridLayoutManger的SapnSize大于5时,ViewHolder是否就会在滚动的过程中一直创建呢?如果是,有没有办法解决这个问题呢?这些东西抛给道友们自己去实践吧!我相信如果仔细看完这篇文章,心中都会有个答案。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿烦大大@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值