看到标题说的是三级缓存,有的地方说是四级缓存,请你不要迷惑,到底是三仍是四,这就像图片加载这个场景有人说是三级缓存有人说是二级缓存,说三级缓存是把经过网络恳求图片这个环节也以为是一层缓存,你以为这个环节应该不应该属于缓存呢?所以到底是三仍是四不重要,由于逻辑是固定的.

其实假如要比较RecyclerviewScrollView这两个哪个控件运用起来更简略,那必然是ScrollView,而且上手难度彻底不是一个量级的.那为什么ScrollView的出镜率彻底比不上Recyclerview呢?这就要归功于Recyclerview缓存规划和他的可扩展性了.

Recyclerview源码剖析:二、滑动时怎么布局中有提到即便你有再多的childView需求展现,可是Recyclerview只会创造一定数量的childView,那咱们有以下几个问题需求探究一下:

前提:笔直布局的Recyclerview高度为100dp,一切的child高度为10dp,初始化时填充了10个child,这时分手指向上滑动了30dp,然后再向下滑30dp回到默许的方位

  1. 在向上滑动30dp的这个环节中,划出屏幕中的child0child1child2这三个child是否进入到同一个缓存中了?
  2. 在向上滑动的30dp的这个环境中,从屏幕底部滑进来的child10child11child12这三个child都是全新创立的ViewHolder吗?假如不是哪些是用的缓存,哪些是全新创立的?
  3. 在向下滑动30dp回到默许方位的这个环节中,滑进来的的child2child1child0这三个child中哪几个不需求走数据绑定逻辑,哪些需求走数据绑定逻辑?

从上面的三个问题能够看出来都是和缓存相关的,那现在要谈缓存,应该先评论把数据存到缓存里边仍是先评论从缓存中取数据呢?假如标题所说的三级缓存,假如只有一层缓存,先评论存仍是先评论取都不会有太大的区别,可是这儿有三层,假如咱们不能先明白三层的数据怎么来的,直接评论怎么从三层缓存中取数据,然后再触摸的都是自己从未触摸过的API会十分冲击持续阅览源码的信心,所以这儿选择先看假如存数据.

缓存从哪来

缓存从哪来?先不从代码剖析,正常情况下,咱们有哪些场景ViewHolder会从屏幕中”消失”呢?

  • ViewHolder被划出屏幕
  • 调用Adapter.notifyItemRemove()

咱们先以划出屏幕这个场景来讲解,在Recyclerview源码剖析:二、滑动时怎么布局中刚好讲了滑动时怎么布局的,第二篇中也能看出Recyclerview在处理滑动时首要便是为了处理嵌套滑动和过滤滑动的,和布局相关的逻辑全部都在LayoutManager.fill()

LLM.fill()

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    // max offset we should set is mFastScroll + available
    final int start = layoutState.mAvailable;
    //这儿其实我也不是很明白为什么要在开端的时分走一遍收回的逻辑
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        // TODO ugly bug fix. should not happen
        if (layoutState.mAvailable < 0) {
            layoutState.mScrollingOffset += layoutState.mAvailable;
        }
        recycleByLayoutState(recycler, layoutState);
    }
    //核算总共有多少空间能够用来摆放child,remainingSpace的值可谓正可为负
    //为负数的场景:
        //在履行fill()之前会履行,会履行updateLayoutState()其中有这段代码
        // mLayoutState.mAvailable = requiredSpace;
        //        if (canUseExistingSpace) {
        //            mLayoutState.mAvailable -= scrollingOffset;
        //        }
        //首要会把手指滑动的间隔赋值给mAvailable,然后再减去scrollingOffset,所以
        //当scrollingOffset>mAvailable时就会为负数,也就代表其时没有空白的方位需求填充child
        //当scrollingOffset代表其时需求滑动多少间隔能够把最终一个child彻底展现,一个高度为100dp的rv
        //child高度都是15dp,那么屏幕上会有7个child,而且第七个child没有彻底展现,需求滑动5dp才能让
        //第七个child彻底展现,那么此刻假如手指滑动的间隔是2dp,那么第七个child还有3dp的部分在屏幕外面
        //这时分remainingSpace便是负数,代表不需求填充新的child
    //为正数的场景:也便是最终一个child未展现的部分高度<手指滑动的间隔这时分必定就需求填充新的child
    //这样代码很好了解的吧,假如其时最终一个child没彻底展现,可是滑动间隔又小于未展现部分的高度
    //这时分必定不需求填充新的child
    int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
    //一个用来保存每次布局一个child的成果类,比方一个child消费了多少空间
    //是否应该实在的核算这个child消费的空间(预布局的时分有些child尽管消费了空间,
    // 可是不应该不参加真实的空间剩下空间的核算)
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    //只要还有空间和item就进行布局layoutchunk
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        //重置上一次布局child的成果
        layoutChunkResult.resetInternal();
        if (RecyclerView.VERBOSE_TRACING) {
            TraceCompat.beginSection("LLM LayoutChunk");
        }
        //这儿是真实layout child的逻辑
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        if (RecyclerView.VERBOSE_TRACING) {
            TraceCompat.endSection();
        }
        if (layoutChunkResult.mFinished) {
            break;
        }
        //layoutState.mLayoutDirection的值是 1或许-1 所以这儿是 乘法
        //假如是从顶部往底部填充,其时填充的是第三个child 且每个高度是10dp,那么layoutState.mOffset的值
        //便是前次填充时的偏移量 + 这次填充child的高度
        //假如是从底部往顶部填充,那便是次填充时的偏移量 - 这次填充child的高度
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
        /**
         * Consume the available space if:
         * * layoutChunk did not request to be ignored
         * * OR we are laying out scrap children
         * * OR we are not doing pre-layout
         */
        //判别是否要真实的消费其时child参加布局所消费的高度
        //从判别条件中能够看到预布局和这个有关,不过预布局等后面几章会具体说的
        //这儿便是同步现在还剩多少空间能够用来布局
        if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
                || !state.isPreLayout()) {
            //这儿是要点,下面的if判别里边会用到mAvailable的值进行核算
            layoutState.mAvailable -= layoutChunkResult.mConsumed;
            // we keep a separate remaining space because mAvailable is important for recycling
            remainingSpace -= layoutChunkResult.mConsumed;
        }
        //在这个判别内履行滑出去的child进行收回
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
            //我先看下layoutState.mAvailable 什么时分会小于0
            //在履行fill()之前会履行,会履行updateLayoutState()其中有这段代码
            // mLayoutState.mAvailable = requiredSpace;
            //        if (canUseExistingSpace) {
            //            mLayoutState.mAvailable -= scrollingOffset;
            //        }
            //requiredSpace是手指滑动的间隔,所以上面代码的履行后
            //layoutstate.mAvailable = 手指滑动间隔 - scrollingOffset;
            //在这个函数的上面又对mAvailable进行了赋值layoutState.mAvailable -= layoutChunkResult.mConsumed
            //所以现在layoutstate.mAvailable = 手指滑动间隔 - scrollingOffset - layoutChunkResult.mConsumed
            //手指滑动间隔 - scrollingOffset 这个核算是什么呢?它属于核算在布局时的有用手指滑动,比方说
            //最终一个child有5dp的内容在屏幕外没显示出来,这时分向上滑动了6dp,那实际上布局需求重视的填充高度为6dp-1dp(有用滑动)
            //所以也就能看出来来layoutstate.mAvailable<0 便是指有用滑动间隔,小于填充child运用的高度
            if (layoutState.mAvailable < 0) {
                //上面有核算 layoutstate.mAvailable = 手指滑动间隔 - scrollingOffset - layoutChunkResult.mConsumed
                //那经过这个核算之后layoutState.mScrollingOffset  = 手指滑动间隔 - scrollingOffset - layoutChunkResult.mConsumed + layoutChunkResult.mConsumed + scrollingOffset
                //也便是   手指滑动间隔
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            //履行收回相关逻辑
            recycleByLayoutState(recycler, layoutState);
        }
        if (stopOnFocusable && layoutChunkResult.mFocusable) {
            break;
        }
    }
    if (DEBUG) {
        validateChildOrder();
    }
    return start - layoutState.mAvailable;
}

这儿总结一下便是经过核算手指滑动间隔最终一个child没彻底展现的高度 > 0,就代表需求填充一个新的child,有新的child进入屏幕,就有旧的child移除屏幕,那么被移出去的child就需求被收回.收回相关的逻辑操控在recycleByLayoutState()

LLM.recycleByLayoutState()

private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
    if (!layoutState.mRecycle || layoutState.mInfinite) {
        return;
    }
    int scrollingOffset = layoutState.mScrollingOffset;
    int noRecycleSpace = layoutState.mNoRecycleSpace;
    //这儿咱们仍是以笔直布局手指向上滑动场景为例
    //由于手指向上滑动,就需求在底部填充child,所以layoutState.mLayoutDirection != LayoutState.LAYOUT_START
    //就会走到else逻辑中
    if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
        recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
    } else {
        //①
        recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
    }
}
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
        int noRecycleSpace) {
    if (scrollingOffset < 0) {
        if (DEBUG) {
            Log.d(TAG, "Called recycle from start with a negative value. This might happen"
                    + " during layout changes but may be sign of a bug");
        }
        return;
    }
    // ignore padding, ViewGroup may not clip children.
    //在前面核算的成果中scrollingOffset==手指滑动的间隔
    //所以知道榜首个child的bottom小于这个值的都会在这次滑动中划出屏幕
    //那他们自然就要被收回
    final int limit = scrollingOffset - noRecycleSpace;
    final int childCount = getChildCount();
    if (mShouldReverseLayout) {
        for (int i = childCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedEnd(child) > limit
                    || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                // stop here
                recycleChildren(recycler, childCount - 1, i);
                return;
            }
        }
    } else {
        //从顶部榜首个child开端找,找到榜首个child的bottom>scrollingOffset(5dp)的child
        //那么这个child之前的一切child在这次滑动中都会划出屏幕
        //所以要把他们都收回掉
        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;
            }
        }
    }
}

这儿的收回逻辑便是判别一切child只要是botton < 有用滑动 都会被收回,这儿是进行判别要收回哪些child,收回逻辑在recycleChildren()

recycleChildren()

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

这儿的逻辑便是经过遍历child的开始index到给定的end index,然后分别履行收回逻辑,也便是removeAndRecycleViewAt()

Recycerlview.LayoutManager.removeAndRecycleViewAt()

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

这儿是先把要收回的View先从RV中移除,然后再走recycler.recycleView()逻辑进行收回

Recycler.recyclerView()

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()) {
        holder.unScrap();
    } else if (holder.wasReturnedFromScrap()) {
        holder.clearReturnedFromScrapFlag();
    }
    //①这儿将收回逻辑交给了recycleViewHolderInternal()
    recycleViewHolderInternal(holder);
    if (mItemAnimator != null && !holder.isRecyclable()) {
        mItemAnimator.endAnimation(holder);
    }
}
void recycleViewHolderInternal(ViewHolder holder) {
    ...
    final boolean transientStatePreventsRecycling = holder
            .doesTransientStatePreventRecycling();
    @SuppressWarnings("unchecked") final boolean forceRecycle = mAdapter != null
            && transientStatePreventsRecycling
            && mAdapter.onFailedToRecycleView(holder);
    boolean cached = false;
    boolean recycled = false;
    if (sDebugAssertionsEnabled && mCachedViews.contains(holder)) {
        throw new IllegalArgumentException("cached view received recycle internal? "
                + holder + exceptionLabel());
    }
    if (forceRecycle || holder.isRecyclable()) {
        //这是榜首层缓存mViewCacheMax,代表这榜首层缓存最多能够缓存多少View
        //这便是榜首层缓存叫做mCachedViews,咱们是从滑动场景盯梢代码进来的
        //在这儿介绍一下这个缓存CachedViews:是用来缓存被划出屏幕的View
        //这层缓存大小是经过mViewCacheMax这个参数来操控的,默许值是2
        //能够经过RV.setItemViewCacheSize()来修正这层缓存的大小
        //假如咱们把这层缓存设置层无限大,那咱们就相当于完成了一个懒加载的Scrollview
        //再说一下这层缓存的特性:
        //1. 从屏幕中滑出去的View会被缓存在这层缓存中
        //2. 从这层缓存中复用的child,是不需求经过任何处理直接运用的
            //也就代表着有些缓存层,从缓存中取出view后还需求做一些处理,比方数据的从头绑定
        //这层缓存是十分有必要的,而且他是一级缓存,再说一下他存在的必要性,现在RV高度为100dp,每个child高20dp,
        //这是屏幕有5个child,这时分手指向上滑动40dp,那么child0,child1都被移除了屏幕,而且进入了这层缓存中
        //这时分用户手指又向下滑动了40dp,child1,child0分别又进入了屏幕,可是这次child1和child0不是新创立的
        //而是从缓存中直接取的,取出来直接add到recyclerview中,这层缓存首要是针对快速上下滑动场景规划的
        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中的榜首个viewholder
            //由于咱们这层一级缓存的大小是有限的,假如设置成无限大,那就变成一个懒加载的ScrollView
            //尽管懒了,可是并不能节约内存,当数量多了之后内存直接爆炸,所以当有新的child进入其时缓存层时,
            //假如缓存满了,就会把最老的view移入其他缓存层,和LRU缓存一样
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                //①这儿便是把过期的一级缓存移入其他缓存层的逻辑
                recycleCachedViewAt(0);
                cachedViewSize--;
            }
            ...
            //假如超出了缓存数量就把过期的view移入其他缓存层,
            //然后把新的view添加到其时缓存层
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
        ...
}
void recycleCachedViewAt(int cachedViewIndex) {
    if (sVerboseLoggingEnabled) {
        Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
    }
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    if (sVerboseLoggingEnabled) {
        Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
    }
    //这儿便是把view存到其他缓存层的逻辑
    addViewHolderToRecycledViewPool(viewHolder, true);
    //把其时这个过期的child从一级缓存层中删除
    mCachedViews.remove(cachedViewIndex);
}
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
    clearNestedRecyclerViewIfNotNested(holder);
    View itemView = holder.itemView;
    //这个判别应该和辅佐功用相关,不必看
    if (mAccessibilityDelegate != null) {
        AccessibilityDelegateCompat itemDelegate = mAccessibilityDelegate.getItemDelegate();
        AccessibilityDelegateCompat originalDelegate = null;
        if (itemDelegate instanceof RecyclerViewAccessibilityDelegate.ItemDelegate) {
            originalDelegate =
                    ((RecyclerViewAccessibilityDelegate.ItemDelegate) itemDelegate)
                            .getAndRemoveOriginalDelegateForItem(itemView);
        }
        // Set the a11y delegate back to whatever the original delegate was.
        ViewCompat.setAccessibilityDelegate(itemView, originalDelegate);
    }
    //这便是回调设置的收回监听
    if (dispatchRecycled) {
        dispatchViewRecycled(holder);
    }
    //这儿能够看到把holder目标的adapter和RV进行了解绑
    //已然解绑了,那下次从缓存中取出这些缓存的时分必定需求从头绑定
    holder.mBindingAdapter = null;
    holder.mOwnerRecyclerView = null;
    //①
    getRecycledViewPool().putRecycledView(holder);
}

从上面的代码能够看出,一级缓存中的过期View会被移入其他缓存层,真实的移入其他缓存层逻辑被交给了RecycledViewPoolputRecycledView().

RecycledViewPool.putRecycledView()

public void putRecycledView(ViewHolder scrap) {
    //首要获取其时要被收回View的 ViewType,由于RV有多布局这个概念
    //这一层缓存是以ViewType为单元进行缓存的.这儿便是咱们的第三级缓存的\
    //是的是第三级缓存不是第二级缓存,第二级的缓存只有在取的时分才会被触及,
    //所以在存的时分只触及一级缓存和三级缓存,我没有胡扯,后面讲取缓存的时分
    //会评论第二级缓存的.
    //先总体概括一下第三级缓存,不需求彻底了解,只需求有这个概念就好了,后面会经过代码证明的
    //由于第三季缓存是以ViewType为单元的缓存,所以会针对各种不同的ViewType进行缓存
    //咱们要缓存同一种类型的View,咱们首要想到的数据结构必定是List,所以每个ViewType对应一个List
    //现在咱们有很多种ViewType,那就对应很多List,那咱们怎么映射一个ViewType和一个List的关系呢?
    //最简略的计划必定是用Map.对应的结构便是Map<ViewType,List<View>>
    final int viewType = scrap.getItemViewType();
    //①这一步便是 要取对应类型缓存的那个List
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
    if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
        PoolingContainer.callPoolingContainerOnRelease(scrap.itemView);
        return;
    }
    if (sDebugAssertionsEnabled && scrapHeap.contains(scrap)) {
        throw new IllegalArgumentException("this scrap item already exists");
    }
    //这儿会把其时ViewHolder的一切信息进行充值
    scrap.resetInternal();
    scrapHeap.add(scrap);
}
//这儿的逻辑是从map中经过ViewType找到对应的缓存List
//这儿的ScrapData是对List<View>一种包装
private ScrapData getScrapDataForType(int viewType) {
    ScrapData scrapData = mScrap.get(viewType);
    if (scrapData == null) {
        scrapData = new ScrapData();
        mScrap.put(viewType, scrapData);
    }
    return scrapData;
}
//这儿能够看出ScrapData 只是对List<BiewHolder>进行了包装,
//而且添加了一个mMaxScrap特点,进行操控缓存个数
static class ScrapData {
    final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
    int mMaxScrap = DEFAULT_MAX_SCRAP;
    long mCreateRunningAverageNs = 0;
    long mBindRunningAverageNs = 0;
}

总结一下便是,滑动的时分,被划出屏幕的child首要会进入一级缓存CachedViews中,由于一级缓存的规划和LRU共同,所以一级缓存中过期的child会被移入第二级和第三级缓存中,可是第二级缓存只在取的时分被用到,所以榜首级缓存中过期的child会被移入第三层缓存RecycledViewPool中,而第三层缓存是针对各种ViewType来缓存的,所以他的缓存结构是Map<ViewTye,List<ViewHolder>>.

缓存要去哪

Recyclerview源码剖析:一、静态时怎么布局的

Recyclerview源码剖析:二、滑动时布局是怎么填充的

两篇评论怎么填充和布局的时分,在layoutChunk()履行layoutState.next(recycler)其时我说暂时不管直接以为他是new了一个View,从他的入参也能看出来必定是从缓存中取的,由于咱们把View存在recycler中.

LLM.LayoutState.next()

View next(RecyclerView.Recycler recycler) {
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    //这儿直接从Recycler中取了
    final View view = recycler.getViewForPosition(mCurrentPosition);
    //mItemDirection有两个值 1/-1,假如是向填充child,取值便是1,mCurrentPosition就会+1
    //反之便是-1
    mCurrentPosition += mItemDirection;
    return view;
}
//Recyclerview.Recycler.java
public View getViewForPosition(int position) {
    return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    if (position < 0 || position >= mState.getItemCount()) {
        throw new IndexOutOfBoundsException("Invalid item position " + position
                + "(" + position + "). Item count:" + mState.getItemCount()
                + exceptionLabel());
    }
    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) {
        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());
        }
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) Find from scrap/cache via stable ids, if exists
        //这儿是经过id在各个缓存中找child,为什么要有这个逻辑>能不能去掉?
        //咱们以无限循环的Banner场景来谈,一个无限循环的banner,一共有3个轮播图
        //当咱们要展现child3的时分咱们希望展现child0,可是上面的寻觅逻辑
        //都是经过position验证的,所以在展会position3的时分child0是不符合规范的
        //那咱们就能够在这儿经过id在position3的方位展现child0了
        if (mAdapter.hasStableIds()) {
            //这儿边的逻辑我就不带着看了,里边的逻辑和上面简直共同,只是将上面判别position的方位
            //变成id判别
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            if (holder != null) {
                // update position
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true;
            }
        }
        //这儿便是咱们的第二级缓存了,第二级缓存是交给开发者完成的
        //经过Recyclerview.setViewCacheExtension(ViewCacheExtension extension) 
        //ViewCacheExtension是一个接口 View getViewForPositionAndType(@NonNull Recycler recycler, int position,int type)
        //开发者在这儿边返回一个View就代表取到缓存了
        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);
                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 (sVerboseLoggingEnabled) {
                Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                        + position + ") fetching from shared pool");
            }
            //上面没取到数据,就从第三级缓存RecycledViewPool中取数据
            holder = getRecycledViewPool().getRecycledView(type);
            if (holder != null) {
                holder.resetInternal();
                if (FORCE_INVALIDATE_DISPLAY_LIST) {
                    invalidateDisplayListInt(holder);
                }
            }
        }
        //假如第三级缓存中也没取到数据
        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;
            }
            //就调用Adapter.createViewHolder()创立一个
            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 (sVerboseLoggingEnabled) {
                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 (sDebugAssertionsEnabled && 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);
        //这儿进行数据绑定
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    ...
    return holder;
}

从上面的逻辑能够看到,从榜首和第二级缓存都没找到,就会到第三缓存中找,假如依然没有找到,就会调用Adapter来新建一个Viewholder,可是第三级缓存的逻辑都在Recycler中,咱们现在看看第三级缓存是怎么取的.

Recyclerview.Recycler.getRecycledView

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

其实这样一看也很简略,便是经过ViewType从Map中找到对应的缓存List<ViewHolder>,然后遍历List