美文网首页
RecyclerView中ViewHolder的复用回收

RecyclerView中ViewHolder的复用回收

作者: 取了个很好听的名字 | 来源:发表于2021-08-05 11:32 被阅读0次

    前言

    本文是本人学习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。

    相关文章

      网友评论

          本文标题:RecyclerView中ViewHolder的复用回收

          本文链接:https://www.haomeiwen.com/subject/dghuvltx.html