RecyclerView中ViewHolder的复用回收

前言

本文是本人学习RecyclerView的ViewHolder回收复用的过程记录,仅为自己复习时使用。
大家在使用RecyclerView时都会创建自己的Adapter并重写相关的方法。

class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
    //创建ViewHolder
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {

    }

    //ViewHolder绑定数据
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {

    }

    //数据个数
    override fun getItemCount(): Int {

    }
}

这里有一个疑问何时为什么有时候onCreateViewHolder和onBindViewHolder都会调用,有时候只会调用onBindViewHolder呢。看完ViewHolder的复用后你可能会有自己的答案。

ViewHolder的复用

首先从RecyclerView的onTouchEvent说起吧。PS:下面的说明只是说明ViewHolder的调用路径。且该路径不唯一,后面会进行说明。

@Override
    public boolean onTouchEvent(MotionEvent e) {
        

        final int action = e.getActionMasked();
        final int actionIndex = e.getActionIndex();

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
              ......
            } break;

            case MotionEvent.ACTION_POINTER_DOWN: {
              ......
            } break;

            case MotionEvent.ACTION_MOVE: {
                .....
                //调用scrollByInternal方法
                    if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            e)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    if (mGapWorker != null && (dx != 0 || dy != 0)) {
                        mGapWorker.postFromTraversal(this, dx, dy);
                    }
                }
            } break;

        .......

        return true;
    }

在onTouchEvent方法中调用scrollByInternal方法。

 boolean scrollByInternal(int x, int y, MotionEvent ev) {
        int unconsumedX = 0;
        int unconsumedY = 0;
        int consumedX = 0;
        int consumedY = 0;

        consumePendingUpdateOperations();
        if (mAdapter != null) {
            mReusableIntPair[0] = 0;
            mReusableIntPair[1] = 0;
            scrollStep(x, y, mReusableIntPair);
            consumedX = mReusableIntPair[0];
            consumedY = mReusableIntPair[1];
            unconsumedX = x - consumedX;
            unconsumedY = y - consumedY;
        }
        if (!mItemDecorations.isEmpty()) {
            invalidate();
        }

        mReusableIntPair[0] = 0;
        mReusableIntPair[1] = 0;
        dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
                TYPE_TOUCH, mReusableIntPair);
        unconsumedX -= mReusableIntPair[0];
        unconsumedY -= mReusableIntPair[1];
        boolean consumedNestedScroll = mReusableIntPair[0] != 0 || mReusableIntPair[1] != 0;

        // Update the last touch co-ords, taking any scroll offset into account
        mLastTouchX -= mScrollOffset[0];
        mLastTouchY -= mScrollOffset[1];
        mNestedOffsets[0] += mScrollOffset[0];
        mNestedOffsets[1] += mScrollOffset[1];

        if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
            if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) {
                pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
            }
            considerReleasingGlowsOnScroll(x, y);
        }
        if (consumedX != 0 || consumedY != 0) {
            dispatchOnScrolled(consumedX, consumedY);
        }
        if (!awakenScrollBars()) {
            invalidate();
        }
        return consumedNestedScroll || consumedX != 0 || consumedY != 0;
    }

scrollByInternal中又调用了scrollStep方法

 boolean scrollByInternal(int x, int y, MotionEvent ev) {
        int unconsumedX = 0;
        int unconsumedY = 0;
        int consumedX = 0;
        int consumedY = 0;

        consumePendingUpdateOperations();
        if (mAdapter != null) {
            mReusableIntPair[0] = 0;
            mReusableIntPair[1] = 0;
           //调用scrollStep方法
            scrollStep(x, y, mReusableIntPair);
            consumedX = mReusableIntPair[0];
            consumedY = mReusableIntPair[1];
            unconsumedX = x - consumedX;
            unconsumedY = y - consumedY;
        }
        .......
        return consumedNestedScroll || consumedX != 0 || consumedY != 0;
    }

scrollStep中调用了LayoutManager的scrollVerticallyBy方法(这里仅分析LinearLayoutManager的scrollVerticallyBy)。

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

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

        int consumedX = 0;
        int consumedY = 0;  
        //调用到scrollHorizontallyBy方法,该方法是LayoutManager的方法
        if (dx != 0) {
            consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
        }
        if (dy != 0) {
            consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
        }

       
    }

LayoutManager的scrollVerticallyBy方法调用了scrollBy方法

   public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == HORIZONTAL) {
            return 0;
        }
       //调用scrollBy方法
        return scrollBy(dy, recycler, state);
    }

scrollBy中调用了fill方法。

int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || delta == 0) {
            return 0;
        }
        ensureLayoutState();
        mLayoutState.mRecycle = true;
        final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        final int absDelta = Math.abs(delta);
        updateLayoutState(layoutDirection, absDelta, true, state);
       //调用LayoutState的fill方法
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
       ...
        return scrolled;
    }

fill方法调用了layoutChunk方法

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
       ...
        int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.beginSection("LLM LayoutChunk");
            }
           //调用layoutChunk方法
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
           ....
        return start - layoutState.mAvailable;
    }

layoutChunk调用了LayoutState的next方法

 void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        //调用了next方法
        View view = layoutState.next(recycler);
       ...
//添加获取到的View
 if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
    }

LayoutState的next方法中调用了Recycler的getViewForPosition方法

  View next(RecyclerView.Recycler recycler) {
//调用Recycler的getViewForPosition方法
        final View view = 
recycler.getViewForPosition(mCurrentPosition);
        mCurrentPosition += mItemDirection;
        return view;
    }

getViewForPosition方法最终会调用到tryGetViewHolderForPositionByDeadline这个方法,而这个方法说明了RecyclerView中的ViewHolder是如何复用的。再看这个方法之前,让我们先来说一下RecyclerView的分级缓存吧。

public View getViewForPosition(int position) {
            //getViewForPosition调用重载方法
            return getViewForPosition(position, false);
        }
 View getViewForPosition(int position, boolean dryRun) {
            return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
        }

RecyclerView的缓存分为四级:


QQ截图20210804143530.png

scrap

rv之所以要将缓存分成这么多块肯定在功能上是有一定的区分的,它们分别对应不同的使用场景,scrap是用来保存被rv移除掉但最近又马上要使用的缓存,比如说rv中自带item的动画效果。

本质上就是计算item的偏移量然后执行属性动画的过程,这中间可能就涉及到需要将动画之前的item保存下位置信息,动画后的item再保存下位置信息,然后利用这些位置数据生成相应的属性动画。如何保存这些viewholer呢,就需要使用到scrap了,因为这些viewholer数据上是没有改变的,只是位置改变而已,所以放置到scrap最为合适。

稍微仔细看的话就能发现scrap缓存有两个成员mChangedScrap和mAttachedScrap,它们保存的对象有些不一样,一般调用adapter的notifyItemRangeChanged被移除的viewholder会保存到mChangedScrap,其余的notify系列方法(不包括notifyDataSetChanged)移除的viewholder会被保存到mAttachedScrap中。

简单来说就是执行Notify动画时,notifyItemRangeChanged移除的ViewHolder会放入mChangedScrap,其余动画(不包括notifyDataSetChanged)移除的ViewHolder会保存到mAttachedScrap

cached

也是rv中非常重要的一个缓存,就linearlayoutmanager来说cached缓存默认大小为2,它的容量非常小,所起到的作用就是rv滑动时刚被移出屏幕的viewholer的收容所。

因为rv会认为刚被移出屏幕的viewholder可能接下来马上就会使用到,所以不会立即设置为无效viewholer,会将它们保存到cached中,但又不能将所有移除屏幕的viewholder都视为有效viewholer,所以它的默认容量只有2个,当然我们可以通过setViewCacheSize(intviewCount)来改变默认的大小

extension

第三级缓存,这是一个自定义的缓存,没错rv是可以自定义缓存行为的,在这里你可以决定缓存的保存逻辑,但是这么个自定义缓存一般都没有见过具体的使用场景,而且自定义缓存需要你对rv中的源码非常熟悉才行,否则在rv执行item动画,或者执行notify的一系列方法后你的自定义缓存是否还能有效就是一个值得考虑的问题。

所以一般不太推荐使用该缓存,更多的我觉得这可能是google自已留着方便扩展来使用的,目前来说这还只是个空实现而已,从这点来看其实rv所说的四级缓存本质上还只是三级缓存。

Pool

又一个重要的缓存,这也是唯一一个我们开发者可以方便设置的一个(虽然extension也能设置,但是难度大),而且设置方式非常简单,new一个pool传进去就可以了,其他的都不用我们来处理,google已经给我们料理好后事了,这个缓存保存的对象就是那些无效的viewholer,虽说无效的viewholer上的数据是无效的,但是它的rootview还是可以拿来使用的,这也是为什么最早的listview有一个convertView参数的原因,当然这种机制也被rv很好的继承了下来。

pool一般会和cached配合使用,这么来说,cached存不下的会被保存到pool中毕竟cached默认容量大小只有2,但是pool容量也是有限的当保存满之后再有viewholder到来的话就只能会无情抛弃掉,它也有一个默认的容量大小

privatestaticfinalintDEFAULT_MAX_SCRAP = 5;

intmMaxScrap = DEFAULT_MAX_SCRAP;

这个大小也是可以通过调用方法来改变,具体看应用场景,一般来说正常使用的话使用默认大小即可。

有了相关的知识我们再来看tryGetViewHolderForPositionByDeadline的代码。

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
         
            ViewHolder holder = null;
         
            if (mState.isPreLayout()) {
                1.从Recycler的mChangedScrap中寻找ViewHolder
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
  
            if (holder == null) {
               2.如果1中没有找到相关的ViewHolder,从Recycler的mAttachedScrap和mCachedViews中获取ViewHolder
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle holder (and unscrap if relevant) since it can't be used
                        if (!dryRun) {
                            // we would like to recycle this but need to make sure it is not used by
                            // animation logic etc.
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            if (holder.isScrap()) {
                                removeDetachedView(holder.itemView, false);
                                holder.unScrap();
                            } else if (holder.wasReturnedFromScrap()) {
                                holder.clearReturnedFromScrapFlag();
                            }
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrapOrHiddenOrCache = true;
                    }
                }
            }
            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                            + "position " + position + "(offset:" + offsetPosition + ")."
                            + "state:" + mState.getItemCount() + exceptionLabel());
                }
                //获取type类型
                final int type = mAdapter.getItemViewType(offsetPosition);
                //3 2中没有找到相关的ViewHolder根据stableID和type再从Recycler的mAttachedScrap和mCachedViews中获取ViewHolder
                if (mAdapter.hasStableIds()) {
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrapOrHiddenOrCache = true;
                    }
                }
                if (holder == null && mViewCacheExtension != null) {
                    //4 3中没有找到查找自定义的缓存(需自己实现)
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        if (holder == null) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view which does not have a ViewHolder"
                                    + exceptionLabel());
                        } else if (holder.shouldIgnore()) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view that is ignored. You must call stopIgnoring before"
                                    + " returning this view." + exceptionLabel());
                        }
                    }
                }
                if (holder == null) { // fallback to pool
                    if (DEBUG) {
                        Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                                + position + ") fetching from shared pool");
                    }
                    //5 4中也没有找到相关的ViewHolder,到pool中寻找
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        //重置ViewHolder的相关状态
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                //6 从5中也没有获取到ViewHolder
                if (holder == null) {
                    long start = getNanoTime();
                    if (deadlineNs != FOREVER_NS
                            && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
                        // abort - we have a deadline we can't meet
                        return null;
                    }
                    // 7执行adaper的mAdapter.createViewHolder去创建相应的ViewHolder,
                    //该方法最终会调用到我们重写的onCreateViewHolder
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    if (ALLOW_THREAD_GAP_WORK) {
                        // only bother finding nested RV if prefetching
                        RecyclerView innerView = findNestedRecyclerView(holder.itemView);
                        if (innerView != null) {
                            holder.mNestedRecyclerView = new WeakReference<>(innerView);
                        }
                    }

                    long end = getNanoTime();
                    mRecyclerPool.factorInCreateTime(type, end - start);
                    if (DEBUG) {
                        Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
                    }
                }
            }

           ......

            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);
                //8  tryBindViewHolderByDeadline会调用adapter的bindViewHolder,最终会调用我们重写的onBindViewHolder方法执行数据的绑定
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }

            ....
            return holder;
        }

现在看一下各个方法吧
ViewHolder getChangedScrapViewForPosition(int position)

 ViewHolder getChangedScrapViewForPosition(int position) {
            // If pre-layout, check the changed scrap for an exact match.
            final int changedScrapSize;
            if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
                return null;
            }
            // 通过position在mChangedScrap找寻对应的ViewHolder
            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;
                }
            }
            // 没有找到再通过id找一篇
            if (mAdapter.hasStableIds()) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
                    final long id = mAdapter.getItemId(offsetPosition);
                    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;
        }

getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun)

 ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
            final int scrapCount = mAttachedScrap.size();

            // 从mAttachedScrap中找
            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;
                }
            }

            if (!dryRun) {
                View view = mChildHelper.findHiddenNonRemovedView(position);
                if (view != null) {
                    // This View is good to be used. We just need to unhide, detach and move to the
                    // scrap list.
                    final ViewHolder vh = getChildViewHolderInt(view);
                    mChildHelper.unhide(view);
                    int layoutIndex = mChildHelper.indexOfChild(view);
                    if (layoutIndex == RecyclerView.NO_POSITION) {
                        throw new IllegalStateException("layout index should not be -1 after "
                                + "unhiding a view:" + vh + exceptionLabel());
                    }
                    mChildHelper.detachViewFromParent(layoutIndex);
                    scrapView(view);
                    vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                            | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    return vh;
                }
            }

            // 找不到从mCachedViews中找
            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);
                    }
                    if (DEBUG) {
                        Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
                                + ") found match in cache: " + holder);
                    }
                    return holder;
                }
            }
            return null;
        }

getScrapOrCachedViewForId(long id, int type, boolean dryRun)

/**
根据id和viewType再从mAttachedScrap和mCachedViews中找一遍
**/ 
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
            // Look in our attached views first
            final int count = mAttachedScrap.size();
            //从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()) {
                        holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                        if (holder.isRemoved()) {
                            // this might be valid in two cases:
                            // > item is removed but we are in pre-layout pass
                            // >> do nothing. return as is. make sure we don't rebind
                            // > item is removed then added to another position and we are in
                            // post layout.
                            // >> remove removed and invalid flags, add update flag to rebind
                            // because item was invisible to us and we don't know what happened in
                            // between.
                            if (!mState.isPreLayout()) {
                                holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
                                        | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
                            }
                        }
                        return holder;
                    } else if (!dryRun) {
                        // if we are running animations, it is actually better to keep it in scrap
                        // but this would force layout manager to lay it out which would be bad.
                        // Recycle this scrap. Type mismatch.
                        mAttachedScrap.remove(i);
                        removeDetachedView(holder.itemView, false);
                        quickRecycleScrapView(holder.itemView);
                    }
                }
            }

            // 从mCachedViews中找
            final int cacheSize = mCachedViews.size();
            for (int i = cacheSize - 1; i >= 0; i--) {
                final ViewHolder holder = mCachedViews.get(i);
                if (holder.getItemId() == id && !holder.isAttachedToTransitionOverlay()) {
                    if (type == holder.getItemViewType()) {
                        if (!dryRun) {
                            mCachedViews.remove(i);
                        }
                        return holder;
                    } else if (!dryRun) {
                        recycleCachedViewAt(i);
                        return null;
                    }
                }
            }
            return null;
        }

getViewForPositionAndType(this, position, type),自定义缓存需要自己实现(不精通RecyclerView源码还是洗洗睡了吧)

 public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
                int type);

总结一下,RecyclerView依次从mChangedScrap、mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool中获取相应的ViewHolder,获取不到就自己创建ViewHolder,最后绑定数据到相应的ViewHolder。
这里需要注意的有一下几点:
1.mRecyclerPool中缓存的ViewHolder只有类型没有相关的数据,需要进行数据绑定
2.ViewHolder的复用导致自定义adapter中的onBindViewHolder和onCreateViewHolder调用次数不一致。
3.ViewHolder是View的一层疯转

方法调用链
入口:RecyclerView滑动 Move 事件 -->RecyclerView. scrollByInternal --> RecyclerView.scrollStep --> LayoutManager.scrollVerticallyBy
-->LayoutManager.scrollBy --> LayoutManager.fill --> LayoutManager.LayoutState.layoutChunk -->LayoutManager.LayoutState.next -->LayoutManager. addView(view);

layoutState.next --> Recycler.getViewForPosition --> Recycler.tryGetViewHolderForPositionByDeadline -->

怎么从集合中去获取:tryGetViewHolderForPositionByDeadline,分几种情况去获取ViewHolder

  1. getChangedScrapViewForPosition -- mChangeScrap 与notfitupdate动画相关
  2. getScrapOrHiddenOrCachedHolderForPosition -- mAttachedScrap 、mCachedViews
  3. getScrapOrCachedViewForId -- mAttachedScrap 、mCachedViews (ViewType,itemid)
  4. mViewCacheExtension.getViewForPositionAndType -- 自定义缓存
  5. getRecycledViewPool().getRecycledView -- 从缓冲池里面获取

除了从onTouchEvent这一条调用路境外,还有一条调用路径
RecyclerView.onLayout-->RecyclerView.dispatchLayout-->RecyclerView.dispatchLayoutStep2-->LayoutManager.onLayoutChildren->LayoutManager.fill->后面与前面的一致。

不管进入的调用链如何,最后都调用到了Recycler.tryGetViewHolderForPositionByDeadLine方法,根据四级缓存来获取活创建ViewHolder

ViewHolder的回收

复用的ViewHolder来自于四级缓存缓存的ViewHolder,那么四级缓存缓存的ViewHolder来自于哪里呢?看看ViewHolder的回收吧。
既然复用是从onTouchEvent进行分析的,这次我们从onLayout开始分析。
onLayout方法调用dispatchLayout方法

 protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
         //调用dispatchLayout
        dispatchLayout();
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
    }

dispatchLayout调用dispatchLayoutStep2方法

void dispatchLayout() {
        ....
        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
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
    }

dispatchLayoutStep2调用LayoutManager的onLayoutChildren,这里分析LinearLayoutManager的onLayoutChildren方法

private void dispatchLayoutStep2() {
        startInterceptRequestLayout();
        onEnterLayoutOrScroll();
        mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
        mAdapterHelper.consumeUpdatesInOnePass();
        mState.mItemCount = mAdapter.getItemCount();
        mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

        // Step 2: Run layout
        mState.mInPreLayout = false;
        //调用onLayoutChildren方法
        mLayout.onLayoutChildren(mRecycler, mState);

        mState.mStructureChanged = false;
        mPendingSavedState = null;

        // onLayoutChildren may have caused client code to disable item animations; re-check
        mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
        mState.mLayoutStep = State.STEP_ANIMATIONS;
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
    }

onLayoutChildren调用detachAndScrapAttachedViews方法

 public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        ...
         //调用detachAndScrapAttachedViews
        detachAndScrapAttachedViews(recycler);
       ...
    }

detachAndScrapAttachedViews调用scrapOrRecycleView

 public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
            final int childCount = getChildCount();
            for (int i = childCount - 1; i >= 0; i--) {
                final View v = getChildAt(i);
                //调用scrapOrRecycleView
                scrapOrRecycleView(recycler, i, v);
            }
        }

scrapOrRecycleView是ViewHolder回收的核心方法

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);
                //缓存进mCacheViews或者mRecyclerPool中
                recycler.recycleViewHolderInternal(viewHolder);
            } else {
                //发生过改变(动画引起的改变)
                detachViewAt(index);
                //缓存进mChangedScrap或mAttachedScrap中
                recycler.scrapView(view);
                mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
            }
        }

ViewHolder如果发生过改变(由notify动画引起的)则会进入
scrapView方法进行缓存否则进入recycleViewHolderInternal方法进行缓存。现在我们先看recycleViewHolderInternal方法是如何缓存数据的

void recycleViewHolderInternal(ViewHolder holder) {
            .....
            if (forceRecycle || holder.isRecyclable()) {
               //如果mViewCacheMax (mCacheViews的最大长度,默认为2)大于0且ViewHolder没发生过变化
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                     //mCachedViews长度大于等于最大长度且mCachedViews中有数据
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                         //将第一个元素放入mRecyclerPool中
                        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;
                    }
                    //将ViewHolder放入mCahceViews的适当位置
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                //如果没有放入mCacheViews,则加入mRecyclerPool中
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } else {
                // NOTE: A view can fail to be recycled when it is scrolled off while an animation
                // runs. In this case, the item is eventually recycled by
                // ItemAnimatorRestoreListener#onAnimationFinished.

                // TODO: consider cancelling an animation when an item is removed scrollBy,
                // to return it to the pool faster
                if (DEBUG) {
                    Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                            + "re-visit here. We are still removing it from animation lists"
                            + exceptionLabel());
                }
            }
            // even if the holder is not removed, we still call this method so that it is removed
            // from view holder lists.
            mViewInfoStore.removeViewHolder(holder);
            if (!cached && !recycled && transientStatePreventsRecycling) {
                holder.mOwnerRecyclerView = null;
            }
        }

再看一下addViewHolderToRecycledViewPool这个方法

 void recycleCachedViewAt(int cachedViewIndex) {
            .....
           //添加viewHolder到mRecyclerPool中
            addViewHolderToRecycledViewPool(viewHolder, true);
           //移除对应位置的ViewHolder
            mCachedViews.remove(cachedViewIndex);
        }

再看一下addViewHolderToRecycledViewPool这个方法
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
....
//ViewHolder放入mRecyclerPool
getRecycledViewPool().putRecycledView(holder);
}
最后看一下putRecycledView这个方法

  public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            //根据type获取对应的集合
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            //如果对应集合的长度大于等于最大长度(默认为5),则直接丢弃该ViewHolder,不进行保存
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
           //重置该ViewHolder相关状态
            scrap.resetInternal();
           //将该ViewHolder加入集合
            scrapHeap.add(scrap);
        }

现在我们总结一下recycleViewHolderInternal都做了那些工作
1.判断mCacheViews是否大于默认的最大大小(默认为2),如果大于则将第一个ViewHolder放入mRecyclerPool中,再讲待回收的ViewHolder放入
mCacheViews的适当位置,如果待回收ViewHoldr没有放入mCacheViews,则放入mRecyclerPool。mRecyclerPool在添加时如果超过了对应viewType的默认集合大小(默认为5),则直接丢弃该ViewHolder,否则重置该ViewHolder的状态并放入对应viewType的集合中。
需要注意的点:
1.mRecyclerPool中的ViewHolder来自于mCacheViews
2.mCacheViews是一个list充当的队列
3.mRecyclerPool中的ViewHolder都是不带数据的

分析完recycleViewHolderInternal我们再看一下scrapView方法

void scrapView(View view) {
            //找到对应的ViewHolder
            final ViewHolder holder = getChildViewHolderInt(view);
             //如果是notify的update动画
            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);
                //将viewHolder添加入mAttachedScrap
                mAttachedScrap.add(holder);
            } else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                //除了update动画之外,将viewHolder添加入mChangedScrap
                holder.setScrapContainer(this, true);
                mChangedScrap.add(holder);
            }
        }

scrapView只要执行了将ViewHolder是否是notify的update动画,如果是则加入mChangedScrap,否则加入mAttachedScrap中。

分析回收方法的方法调用链
LinearLayoutManager.onLayoutChildren --> detachAndScrapAttachedViews --> scrapOrRecycleView

--> 1.recycler.recycleViewHolderInternal(viewHolder); -- 处理 CacheView 、RecyclerViewPool 的缓存

--> 1.ViewHodler改变 不会进来 -- 先判断mCachedViews的大小

    --> mCachedViews.size 大于默认大小  --- recycleCachedViewAt 
    --- >addViewHolderToRecycledViewPool --- 缓存池里面的数据都是从mCachedViews里面出来的

--> 2.addViewHolderToRecycledViewPool --> getRecycledViewPool().putRecycledView(holder);

    --> scrap.resetInternal();  ViewHolder 清空

--> 2.recycler.scrapView(view);

还有一个回收的方法调用链调用是在onTouchEvent开始:
RecyclerView.onTouchEvent(move事件)-->RecyclerView.scrollByInternal->RecyclerView.scrollStep-->LayoutManager.mLayout.scrollVerticallyBy(仅分析竖直方向的滑动,以LinearLayoutManager.scrollVerticallyBy为例)-->LayoutManager.scrollBy-->LayoutManager.fill-->LayoutManager.recycleByLayoutState-->LayoutManager.recycleByLayoutState-->LayoutManager.recycleViewsFromStart-->LayoutManager.recycleChildren-->LayoutManager.removeAndRecycleViewAt-->Recycler.recycleView-->recycler.recycleViewHolderInternal
这里需要注意的一点,在onTouchEvent这条线中,并没有调用scapView这个方法,原因是scapView这个方法是判断回收的ViewHolder是否发生过改变,而ViewHolder发生改变是通过notify的动画来实现的,而notify动画的改变会引起RecyclerView的重新布局(重新调用onLayout方法,所以onLayout的那条调用链上存在scapView这个方法),而滑动不会引起动画,所以不需要scapView方法,仅调用recycleViewHolderInternal即可

总结

touchEvent和onLayout都会执行ViewHolder的回收复用,所不同的是onLayout的回收要考虑Item动画的,而onTouchEvent的回收不需要考虑动画的,因为执行动画会重新调用onLayout,所以在onLayout时会考虑item动画移除的ViewHolder。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容