缓存——RecyclerView源码详解(二)

刚才有个朋友问我,博主发生什么事了,给我发了几张截图,我一看,哦,原来是有个大帅哔看了文章,说是,博主,我能白嫖你的文章,我说年轻人,点个赞再走,他说不点,我说点一个,他说不点,我说点一个,他说不点,我说我这文章对你有用,他不服气,说要先看看。我说可以,很快啊,看完后,就是一个复制,一个粘贴,一个网页关闭,我大意了啊,没有删除文章。按传统博客的有用为止,他说已经输了啊。 后来他说他是乱点的,这可不是乱点的啊,训练有素。我劝年轻人好好点赞,耗子尾汁,谢谢朋友们

前言

整体流程、measure、layout 详解 ——深入分析RecyclerView源码(一)
缓存 ——深入分析RecyclerView源码(二)

这篇文章,分析一下RecycleView 的缓存机制,分为两大部分: 从缓存获取View和 把View保存到缓存中

缓存的核心是交给 Recycler 类来处理的,包括存储缓存,获取缓存等。缓存的数据类型是ViewHold,它包含了itemView,mPosition 等Item的信息

从缓存获取ViewHold

先来了解Recycler中五个变量,每个变量都与缓存相关,按照缓存获取的顺序来逐一介绍

一级缓存:返回布局和内容都都有效的ViewHolder

  • 按照position或者id进行匹配
  • 命中一级缓存无需onCreateViewHolder和onBindViewHolder
  1. mAttachedScrap ArrayList : 未与RecyclerView分离的ViewHolder列表。如果仍依赖于 RecyclerView (比如已经滑动出可视范围,但还没有被移除掉),但已经被标记移除的 ItemView 集合会被添加到 mAttachedScrap 中按照id和position来查找ViewHolder。在每次绘制RecycleView的时候,都会先把界面上的ViewHolder收集到mAttachedScrap,然后在绘制的时候,方便复用

  2. mChangedScrap ArrayList:表示数据已经改变的viewHolder列表,存储 notifXXX 方法时需要改变的 ViewHolder,匹配机制按照position和id进行匹配

  3. mCachedViews ArrayList:缓存ViewHolder,主要用于解决RecyclerView滑动抖动时的情况,还有用于保存Prefetch的ViewHoder。

    • 位置相同的ViewHolder,才能复用。复用的ViewHolder,不需要bindViewHolder,可直接拿去绘制。
    • 最大的数量为:mViewCacheMax = mRequestedCacheMax + extraCache(extraCache是由prefetch的时候计算出来的)

二级缓存:返回View

  • 按照position和type进行匹配
  • 直接返回View
  1. mViewCacheExtension ViewCacheExtension:开发者可自定义的一层缓存,是虚拟类ViewCacheExtension的一个实例,开发者需实现方法getViewForPositionAndType(Recycler recycler, int position, int type),注意它返回的是View。

三级缓存:返回布局有效,内容无效的ViewHolder

  • layout是有效的,但是内容是无效的,需要调用bindViewHolder
  • 多个RecycleView可共享,可用于多个RecyclerView的优化
  1. mRecyclerPool ViewHolder缓存池。 当mCachedViews缓存数量达到上限,就会把mCachedViews中的一个ViewHolder存入RecyclerViewPool中,把当前的ViewHolder存入mCachedViews。
    • 按照Type来查找ViewHolder
    • 每个Type默认最多缓存5个。

这里我们在上一篇文章的layoutChunk 函数开始分析,获取一个View,然后进行布局。

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        // 获取View,如果缓存中没有,就新创建
        View view = layoutState.next(recycler);
       
       //省略代码
    }
在LayoutState类中
        //返回下一个位置的View
        View next(RecyclerView.Recycler recycler) {
            if (mScrapList != null) {
                return nextViewFromScrapList();
            }
            //通过Recycle,来获取View
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            return view;
        }

recycler.getViewForPosition(mCurrentPosition); 最终会调用到 tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS)
后者返回的是ViewHolder。下面就来分析一下这个函数,它是获取缓存的重点函数

在Recycle类中
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            ...  省略代码 ...
            boolean fromScrapOrHiddenOrCache = false;
            ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 1) Find by position from scrap/hidden list/cache
            if (holder == null) {
                // 先从mAttachedScrap、Hidden、mCachedViews中 查找,位置是position 的ViewHolder 
                //这里获取到指定position的缓存,该类型的缓存,可直接复用,不需要重新onBindViewHolder
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                if (holder != null) {
                    //检查ViewHolder 是否可用;位置是否正确;itemViewType是否匹配;若item有固定id(Stable Id),该id是否正确
                    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;
                    }
                }
            }
            //判断是否获取到了缓存的ViewHolder,如果为空,就在mCachedViews 中进行获取
            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());
                }

                final int type = mAdapter.getItemViewType(offsetPosition);
                // 2) Find from scrap/cache via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    //如果存在固定id,则去mAttachedScrap、mCachedViews 中去查找 是否有对应的ViewHolder
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrapOrHiddenOrCache = true;
                    }
                }
                // 还没有获取到缓存 ViewHolder,就去mViewCacheExtension 去获取,这个是自定义的缓存,可能为空
                if (holder == null && mViewCacheExtension != null) {
                    // We are NOT sending the offsetPosition because LayoutManager does not
                    // know it.
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        。。。省略代码。。。
                    }
                }
                //还是没有获取到缓存,就去RecycledViewPool 按照itemView Type 查找,
                //找到的缓存,因为可能位置可能,所以需要重新onBinderViewHolder 的,
                if (holder == null) { // fallback to pool
                    。。。省略代码。。。
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                //到此,所有的缓存都查找过了,还是没有的话,就去createViewHolder 创建一个新的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;
                    }
                    //这个函数,大家应该很熟悉了
                    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);
                    。。。省略代码。。。
                }
            }

            。。。省略代码。。。

            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()) {
                // 根据holder的mFlags 标志,来判断当前ViewHolder的状态。mFlags 是Int 类型,每个二进制位,表示一种状态
                //mFlags 默认是0 ,也就是任何状态都是 0
                //在下面的缓存回收机制,可以看到如果有ViewHolder.FLAG_INVALID、ViewHolder.FLAG_UPDATE 这里两种状态
                //是不能加入mCachedViews中的,所以也就是说mCachedViews 获取到的缓存是不需要onBindViewHolder
                。。。省略代码。。。
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                //调用到onBindViewHolder,来把数据绑定到View
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }
            //获取ViewHolder 的LayoutParams,
            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            //这个LayoutParams 是RecycleView 自定义的,继承于android.view.ViewGroup.MarginLayoutParams
            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;
        }

其中涉及到缓存查找的几个函数,例如 getScrapOrHiddenOrCachedHolderForPositiongetScrapOrCachedViewForId等,基本思路就是变量相关的缓存变量,来查找符合条件的ViewHolder。 这里就不展开了,下面来看一下ViewHolder 是如何被回收到这些缓存中的

回收ViewHold到缓存

缓存的获取,很简单,按照一定的顺序,在不同的缓存层级查找。回收 才代表缓存内部的逻辑,保存到哪个缓存层级,以及何时被转移。缓存回收有很多中情况,下面我们以 item 滑出屏幕 为例,来分析缓存的回收

先来一张图,对整体的回收逻辑 有个认识
在这里插入图片描述

从滑动事件的入口,来分析

在RecycleView 类中
    @Override
    public boolean onTouchEvent(MotionEvent e) {

      。。。省略代码。。。    
      switch (action) {
              。。。省略代码。。。
              //滑动操作,
              case MotionEvent.ACTION_MOVE: {
                。。。省略代码。。。
                final int index = e.findPointerIndex(mScrollPointerId);
              
                if (mScrollState == SCROLL_STATE_DRAGGING) {
                    mLastTouchX = x - mScrollOffset[0];
                    mLastTouchY = y - mScrollOffset[1];
                    //这个是重点
                    if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            vtev)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    //调用预布局等操作
                    if (mGapWorker != null && (dx != 0 || dy != 0)) {
                        mGapWorker.postFromTraversal(this, dx, dy);
                    }
                }
            } break;
      }
    }

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

        consumePendingUpdateOperations();
        if (mAdapter != null) {
            //重点函数
            scrollStep(x, y, mScrollStepConsumed);
            consumedX = mScrollStepConsumed[0];
            consumedY = mScrollStepConsumed[1];
            unconsumedX = x - consumedX;
            unconsumedY = y - consumedY;
        }
        if (!mItemDecorations.isEmpty()) {
            invalidate();
        }
        //处理嵌套滑动
        if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
                TYPE_TOUCH)) {
            // Update the last touch co-ords, taking any scroll offset into account
            mLastTouchX -= mScrollOffset[0];
            mLastTouchY -= mScrollOffset[1];
            if (ev != null) {
                ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
            }
            mNestedOffsets[0] += mScrollOffset[0];
            mNestedOffsets[1] += mScrollOffset[1];
        } else 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 consumedX != 0 || consumedY != 0;
    }
    void scrollStep(int dx, int dy, @Nullable int[] consumed) {
        //开始拦截 ,避免多余的requestLayout。主要是设置变量mInterceptRequestLayoutDepth,开启拦截就+1,关闭就-1
        //如果该值为0,表示该条件 允许绘制
        startInterceptRequestLayout();
        //表示进入布局或滚动,此时不能改变adapter的数据,否则会抛出异常。mLayoutOrScrollCounter,进入+1,退出-1
        onEnterLayoutOrScroll();

        TraceCompat.beginSection(TRACE_SCROLL_TAG);
        fillRemainingScrollValues(mState);
        //在x y 轴上 滑动消耗的值
        int consumedX = 0;
        int consumedY = 0;
        //下面这两个滑动函数,调用到LayoutManager中,
        if (dx != 0) {
            consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
        }
        if (dy != 0) {
            consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
        }

        TraceCompat.endSection();
        repositionShadowingViews();
        //退出布局或滚动
        onExitLayoutOrScroll();
        //关闭拦截 ,允许的requestLayout
        stopInterceptRequestLayout(false);

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

下面来以scrollVerticallyBy 为例,来分析,下面的函数进入了LinearLayoutManager.java 中

在LinearLayoutManager.java中
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == HORIZONTAL) {
            return 0;
        }
        return scrollBy(dy, recycler, state);
    }
    
    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || dy == 0) {
            return 0;
        }
        mLayoutState.mRecycle = true;
        //确保LayoutState 不为空,LayoutState 是保存
        ensureLayoutState();
        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);
        if (consumed < 0) {
            if (DEBUG) {
                Log.d(TAG, "Don't have any more elements to scroll");
            }
            return 0;
        }
        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
        mOrientationHelper.offsetChildren(-scrolled);

        mLayoutState.mLastScrollDelta = scrolled;
        return scrolled;
    }

关于fill 函数,在整体流程、Measure、Layout 详解——RecyclerView源码详解(一) 疑问已经分析过了,它的返回值,表示的是填充的值(即使没有显示在屏幕上),在有回收ViewHolder的功能 recycleByLayoutState(recycler, layoutState);

在LinearLayoutManager.java中
    private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (!layoutState.mRecycle || layoutState.mInfinite) {
            return;
        }
        //根据不同的方向,因为本例是垂直方向,所以LAYOUT_START 表示从上往下
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
        } else {
            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
        }
    }
    private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
        。。。省略代码。。。
        // ignore padding, ViewGroup may not clip children.
        final int limit = dt;
        final int childCount = getChildCount();
        if (mShouldReverseLayout) {
           //列表翻转,从上到下是 n-1,n-2,。。。2,1,0, 此时函数是recycleViewsFromStart,也就是从上往下回收,所以最先回收的是n-1
            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 {
            //同上面逻辑一样,列表正常显示,从上到下是 0,1,2。。。n-2,n-1这里最先回收的是 所以 0 的位置
            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;
                }
            }
        }
    }

整体流程、Measure、Layout 详解——RecyclerView源码详解(一) 详细分析过了mShouldReverseLayout 表示 列表是否翻转,例如 原来是顺序 ,改为倒序

上面是根据不同的布局方向,选择回收哪一个。最终回收代码是在recycleChildren

在LinearLayoutManager.java中
    private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
        if (startIndex == endIndex) {
            return;
        }
        if (endIndex > startIndex) {
            for (int i = endIndex - 1; i >= startIndex; i--) {
                //调用到父类LayoutManager 的实现
                removeAndRecycleViewAt(i, recycler);
            }
        } else {
            for (int i = startIndex; i > endIndex; i--) {
                removeAndRecycleViewAt(i, recycler);
            }
        }
    }

继续分析 removeAndRecycleViewAt 函数

在LayoutManager 类中
        public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
            final View view = getChildAt(index);
            //
            removeViewAt(index);
            //调用到Recycle 类中
            recycler.recycleView(view);
        }

继续到Recycle中分析 recycleView

在Recycle 类中

        public void recycleView(@NonNull View view) {
            // This public recycle method tries to make view recycle-able since layout manager
            // intended to recycle this view (e.g. even if it is in scrap or change cache)
            ViewHolder holder = getChildViewHolderInt(view);
            if (holder.isTmpDetached()) {
                removeDetachedView(view, false);
            }
            if (holder.isScrap()) {
               //从mChangedScrap 或mAttachedScrap 删除该holder
               //因为这些view 已经detached
                holder.unScrap();
            } else if (holder.wasReturnedFromScrap()) {
                holder.clearReturnedFromScrapFlag();
            }
            //缓存到mAttachedScrap 、RecycleViewPool 中
            recycleViewHolderInternal(holder);
        }
在Recycle 类中
        void recycleViewHolderInternal(ViewHolder holder) {
            。。。省略代码。。。
            //noinspection unchecked
            final boolean transientStatePreventsRecycling = holder
                    .doesTransientStatePreventRecycling();
            final boolean forceRecycle = mAdapter != null
                    && transientStatePreventsRecycling
                    && mAdapter.onFailedToRecycleView(holder);
            boolean cached = false;
            boolean recycled = false;
            
            if (forceRecycle || holder.isRecyclable()) {
                //mViewCacheMax 表示最大的缓存数量,默认是2。
                // holder 不能存在以下的几种状态,在上面获取缓存holder后,如果有ViewHolder.FLAG_INVALID、ViewHolder.FLAG_UPDATE,就需要重新绑定数据。而又这两个状态,是不能加入mCachedViews中的
                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();
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        //从mCachedViews 中删除0 位置的 索引,并把0 位置的缓存存储到RecycleViewPool
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }
                    //准备把当前的ViewHolder 缓存到mCachedViews
                    int targetCacheIndex = cachedViewSize;
                    。。。省略代码。。。
                    //添加缓存
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                // 如果缓存到mCachedViews没有成功,
                // 说明上面的if没有进去,要么缓存数量设置小于0,要么holder的状态不符合要求              
                if (!cached) {
                    //缓存到 RecycledViewPool
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } else {
                 。。。省略代码。。。
            }
            // 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;
            }
        }

mViewCacheMax 值可通过setViewCacheSize来手动设置最大缓存,

下面来看下 ViewHolder 是如何加入到RecycledViewPool 中的

        void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
            //如果ItemView 中嵌套了 RecycleView(会存在变量mNestedRecyclerView) ,把子RecycleView 清空,
            clearNestedRecyclerViewIfNotNested(holder);
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) {
                holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);
                ViewCompat.setAccessibilityDelegate(holder.itemView, null);
            }
            //调用相关的回调接口
            if (dispatchRecycled) {
                dispatchViewRecycled(holder);
            }
            holder.mOwnerRecyclerView = null;
            //按照viewType 分类保存起来
            getRecycledViewPool().putRecycledView(holder);
        }

RecycledViewPool 中的缓存,是按照viewType 来分类保存的,viewType 是在createViewHolder 的时候 传入的,下面来看一下,是如何保存到RecycledViewPool

        public void putRecycledView(ViewHolder scrap) {
            // 获取viewType
            final int viewType = scrap.getItemViewType();
            //getScrapDataForType 是获取对应viewType的ScrapData,里面保存了缓存ViewHolder 列表(mScrapHeap),最大缓存等信息
            //再通过变量mScrapHeap,拿到ViewHolder 列表
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            //当前viewType 的viewHolder缓存个数 是否到达上限,默认是5
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            //把当前的viewHodler 重置,就相当于想创建的一个ViewHolder,再次使用的时候,需要调用onBindViewHolder
            scrap.resetInternal();
            //把当前ViewHolder保存起来
            scrapHeap.add(scrap);
        }

至此,缓存回收的机制主要流程就分析完了,相信有同学会有疑问,为什么回收缓存,没有用到mAttachedScrap、mViewCacheExtension

mAttachedScrap : 在 layout Step 2 阶段时,调用dispatchLayoutStep2() ->mLayout.onLayoutChildren(mRecycler, mState); ->detachAndScrapAttachedViews(recycler);-> scrapOrRecycleView() ->recycler.scrapView(view); -> mAttachedScrap.add(holder);

在开始绘制之前,把当前屏幕上的ViewHolder 缓存到 mAttachedScrap。mLayout.onLayoutChildren -> fill 阶段 ,就会去缓存中先从mAttachedScrap中获取

详细代码流程,可参考整体流程、Measure、Layout 详解——RecyclerView源码详解(一)

mViewCacheExtension 是自定义的缓存,需要实现 getViewForPositionAndType,只会在获取缓存的时候获取,保存缓存时,RecycleView 并不会干预mViewCacheExtension

最后,来一道题 面试常考点之RecyclerView回收和复用机制最全分析,来检验一下是否完全搞懂了,缓存机制

参考:
面试常考点之RecyclerView回收和复用机制最全分析
RecyclerView缓存原理,有图有真相

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页