RecyclerView是如何管理视图的(一) 回收与复用

2,941 阅读17分钟

关于ViewHolder复用的三个问题:

  • 为什么要复用?复用解决了什么问题?
  • 回收时存在哪?
  • 复用时去哪取?

一. Adapter和ViewHolder的使用

谈到RecyclerView的复用机制,就不得不提ViewHolder这一结构,它本质上就是一个包裹了View和一些其他信息的类,RecyclerView中的Recycler组件将会根据ViewHolder提供的其他信息来判断是否进行复用。

Adapter提供了三个需要重写的函数,其中有两个onCreateViewHolder(@NonNull ViewGroup parent, int viewType)onBindViewHolder(@NonNull VH holder, int position)是和ViewHolder密切相关的函数。

其中的onCreateViewHolder()方法的作用是创建ViewHolder,从参数中,我们可以看出,创建的位置是RecyclerView本身,而传入的参数是viewType,RecyclerView支持使用viewType来区分不同类型的视图。

onBindViewHolder()所做的就是将onCreateViewHolder()创建好的一堆ViewHolder,和视图进行绑定。

值得一提的是,在onBindViewHolder()的使用过程中,更推荐的做法是在ViewHolder声明时,就建立好对应的视图引用:

  inner class CustomOriginalViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    var textView: TextView = view.findViewById(R.id.textView)
    var imageView: ImageView = view.findViewById(R.id.imageView)
  }

然后在需要做数据处理的地方,直接调用ViewHolder#textView.setText()为TextView做数据的绑定,而不是在onBindViewHolder()时再调用holder#itemView#findViewById(R.id.xxx)去临时获取控件。原因是,findViewById()本质上也是一个有一定时间消耗的方法,特别是在itemView的布局嵌套较深或者itemView中有大量控件的情况下,如果在进行列表滚动时,我们复用了ViewHolder,每次在bindViewHolder时,去动态查询控件,会产生一定的性能损失。如果能将视图绑定的这种引用关系存储在ViewHolder之下,那么对列表的性能提升有一定的帮助。

二. 缓存结构和回收

Recycler回收复用主要由Recycler完成,回收的对象主要是ViewHolder,在我们的RecyclerView有新的item视图被加载进来的时候,Recycler会优先去缓存中查找是否有合适的项目,简单地通过直接的数据更新即可再次拿出使用。这样一来,大大地减少了创建新的视图的时间成本、存储大量视图的内存成本。

RecylerView主要针对三个场景进行回收,设立了四个回收的缓存等级:

未命名文件 (1).png

2.1 缓存结构

其中,第一层的两个回收等级在使用时,是更常访问的存储结构,类型是使用ArrayList实现的。

mChangedScrap结构主要主要存储一些因为动画原因被淘汰下来的ViewHolder

际断点 + 测试的情况下,如果不启用动画,这个缓存结构基本上是不会被用到的;一旦启动动画,发生Item动画的旧ViewHolder会被缓存进该结构,而不是mAttachScrap.

mAttachScrap则是一个运行时存储结构,在重新布局的情况下,它承担着暂存屏幕上所有(除了动画)ViewHolder的任务。

第二层结构的mCachedViews结构,同样采用ArrayList实现,采用可变大小结构设计,默认情况下,它的大小为2。我们也可以通过方法设置它的缓存大小:RecyclerView#setItemViewCacheSize(size),如果为0,则这一层缓存失效。

第三层的ViewCacheExtension则是在结构上预留给用户实现的缓存结构。

第四层的RecyclerViewPool则是最后一层的缓存,采用SparseArray结构实现。它会根据我们设置的不同的itemType对不同类型的ViewHolder进行缓存。

2.2 缓存和回收

缓存和回收看上去是相同的,但是RecyclerView似乎也在这方面隐晦地做了区分。我们知道Adapter中有一个可以重写的回调函数:public void onViewRecycled(@NonNull VH holder) ,该函数仅会在ViewHolder被添加进最后一层RecyclerViewPool时被回调到。这似乎象征着RecyclerView在并没有将其他的mChangedScrap、mAttachScrap和mCachedView一同视作回收的一部分,因而从RecyclerViewPool中取值和从其他地方取ViewHolder的方式和对待取出ViewHolder的方式有本质的区别。

ItemAnimator的结束也会触发ViewHolder的onViewRecycled。说明因为动画而被淘汰的ViewHolder不经过mCachedView,而直接进入了RecyclerViewPool缓存当中。个人推测的原因是,导致item动画的几种情况:删除、更新Item都需要进行数据的重新绑定,动画淘汰的ViewHolder不可能能够在不重新执行BindViewHolder的情况下,快速复用。

RecyclerView只认为被放入RecyclerViewPool的是回收,而其他的地方都是缓存,区别在于:缓存能够以更低的代价快速复用。这部分相关的逻辑在tryGetViewHolderForPositionByDeadline()函数当中,不论是从哪个「缓存」中取出的数据,都只是简单处理;而从RecyclerViewPool中取出的ViewHolder,却做了额外的一系列操作:

if (holder != null) {
    holder.resetInternal();// 重置ViewHolder中的所有数据,包括itemId和position数据
  	// 特定的系统版本上可能还需要重置DISLAY_LIST
    if (FORCE_INVALIDATE_DISPLAY_LIST) {
        invalidateDisplayListInt(holder);
    }
}

RecyclerViewPool回收的依据是ItemViewType,将同一类的、无效的ViewHolder聚合到一起进行回收,因此,ViewHolder#resetInternal()没有重新设置ItemViewType

而其他的缓存结构使用的标识大多是positionitemId这两个唯一标识,它们复用时,大概率可以精确到具体的某一个最近仍然在使用的ViewHolder,实现更多视图数据的重用。

2.3 流程分析

image-20211221112238167.png

背景:此时屏幕上含有19个(标号为0~18,18是显示不完整的,有一部分在屏幕之外)item。

  • 当第一次下滑一个item高度时,新的ViewHolder-19被加入显示,处于0位置的ViewHolder被从屏幕上detach了,此时进入mCachedViews缓存;
  • 再向下滑动一个Item高度,新的ViewHolder-20进入屏幕显示,处于1位置的ViewHolder被从屏幕上detach了,此时进入mCachedViews缓存「1」,此时的mCachedViews缓存已经满了;
  • 再向下滑动一个Item高度时,新的ViewHolder-21进入屏幕显示,mCachedViews已满,最早进入的item0被回收了,进入RecyclerViewPool,被淘汰的ViewHolder2进入mCachedViews「2」

额外拓展出两种情况:

「1」(VIewHolder还未被回收,处于mCachedViews中),如果此时向上滑,让已经被detach的ViewHolder0重新出现;

image-20211221113106768.png

RecyclerView直接attach到屏幕上显示了;

「2」(ViewHolder已经被回收到RecyclerViewPool中),操作同上;

image-20211221113338549.png RecyclerViewRecyclerViewPool中取出了ViewHolder-0,并且对它重新做了数据绑定;

三. 缓存、复用宏观流程分析

在开始介绍缓存和复用之前,我们必须知道,什么情况下RecyclerView会触发缓存和复用(什么时候存进去和什么时候取出来)。

首先,谈谈复用。复用,即将之前淘汰而未销毁的的ViewHolder取出继续使用。我们知道,RecyclerView将布局的任务,交给了LayoutManager这一控件,LayoutManager会根据自己的不同的实现,来实现线性、格栅、瀑布流等多种布局。列表控件中,LayoutManager会不断地向Recycler获取item,将RecyclerView的视图填满,这一步的实现在LayoutManagerfill()函数中,在其中调用了layoutChunk()函数。

LayoutManager会访问Recycler的相关方法(调用链最终走到tryGetViewHolderForPositionByDeadline()方法),返回一个ViewHolder,然后取它的itemView,将其返回给layoutChunk(),最终通过ChildHelper的addView方法,向RecyclerView中填充视图。这样一来,我们就知道了RecyclerView在任何一级缓存中的触发复用,一定是和tryGetViewHolderForPositionByDeadline()函数相关的。

而缓存时机,则要区分各种情况来分析,在不知全貌的情况下,最好的分析办法就是查询mAttachScrap、mChangedScrap等等结构在Recycler组件中的调用。

  • mAttachScrapmChangedScrap,因为这俩都是ArrayList,它们都在一个名为scrapView()的方法中被call过,对调用进行溯源,最终的源头又回到了LayoutManager中的onLayoutChildren(),对detachAndScrapAttachedViews(recycler)的调用。其他暂时不关心,我们只需要知道,仅在布局、动画相关的场景下,该一级的两种缓存会分别被使用到。

  • mCachedViews,同样是ArrayList,它的调用也是唯一的,在recycleViewHolderInternal(ViewHolder holder)当中,该方法后面统一去进行说明,它和上面提到的tryGetViewHolderForPositionByDeadline()很像是一个配套的方法,分别负责缓存、复用的主要逻辑。

  • ViewCacheExtension,该方法乍一看很奇怪,它只提供了一个抽象定义的取出的方法,那怎么存入呢?

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

    实际上,因为我们要完全自定义存储的逻辑,那么我们还要定义存储的结构,具体是一个链表还是数组,是集合还是HashMap,这些都需要我们亲力亲为。而是否需要存入缓存也应该由我们的Adapter来配套实现,我们可以自定义一个方法来根据key获取一个缓存的ViewHolder,我们可以在onCreateViewHolder函数中,将所有的ViewHolder都存入这个缓存,也可以在onBindViewHolder函数中,根据特定的数据,选择将ViewHolder存入缓存。

  • RecyclerViewPool,当mCachedViews中的数据已经存满时(默认是达到2个时),根据先进先出原则(FIFO),末尾的ViewHolder将会流入最后一层的RecyclerViewPool,那么它的存入时机也很明显了,就是和上一级mCacheViews的Size相关。但是,在某些场景下,也会直接在recyclerViewHolderInner()函数中直接将ViewHolder存入RecyclerViewPool当中。

    void updateViewCacheSize() {
     		······
        for (int i = mCachedViews.size() - 1;
                i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
            recycleCachedViewAt(i);//注意此处
        }
    }
    
    void recycleCachedViewAt(int cachedViewIndex) {
        ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
        addViewHolderToRecycledViewPool(viewHolder, true);// 存入池子
        mCachedViews.remove(cachedViewIndex);
    }
    
    // 直接在`recyclerViewHolderInner()`函数中直接将**ViewHolder**存入池子
    if (!cached) {
        addViewHolderToRecycledViewPool(holder, true);
        recycled = true;
    }
    

    其实在复用相关方法:tryGetViewHolderForPositionByDeadline()中,也对上面的缓存方法addViewHolderToRecycledViewPool会有调用,这中在复用时进行的回收后面再统一说明。这里主要讨论的是缓存 + 复用的一个完整过程链。

四. 回收

回收主要是来自LayoutManager#removeAndRecycleView系列方法的被动调用,另外还有一些来自复用函数运行时tryGetViewHolderForPositionByDeadline(),Recycler主动对ViewHolder检查,并做的回收。

该段主要是针对public void recycleView(@NonNull View view) recyclerViewHolderInternal()方法的分析:

public void recycleView(@NonNull View view) {

    ViewHolder holder = getChildViewHolderInt(view);
    if (holder.isTmpDetached()) {
      	// 移除掉Detached的视图
        removeDetachedView(view, false);
    }
  	// 先将scrap状态移除
    if (holder.isScrap()) {
        holder.unScrap();
    // 将原先的:从Scrap结构移出的标记清除
    } else if (holder.wasReturnedFromScrap()) {
        holder.clearReturnedFromScrapFlag();
    }
  	// 具体的移除逻辑
    recycleViewHolderInternal(holder);
    if (mItemAnimator != null && !holder.isRecyclable()) {
        mItemAnimator.endAnimation(holder);
    }
}


void recycleViewHolderInternal(ViewHolder holder) {
  // 抛出异常,如下情况的ViewHolder不会被回收:
  // 1. Scrap和被Attach过的,
  // 2. 临时Detach的视图应该在被回收之前先从RecyclerView上Remove
  // 3. 试图移除被ignore的ViewHolder

  // 查询ViewHolder是否有因为Animation导致的过度状态,如果没有则返回True。如果有,则会阻止开始回收。
  final boolean transientStatePreventsRecycling = holder
          .doesTransientStatePreventRecycling();

  @SuppressWarnings("unchecked")
  final boolean forceRecycle = 
              mAdapter != null
          && transientStatePreventsRecycling
// 收到此回调时,Adapter可以清除这些让视图处于过度态的动画,并返回True来保证视图能够被回收,默认不重写的情况下,Adapter会返回false
          && mAdapter.onFailedToRecycleView(holder);


  boolean cached = false;
  boolean recycled = false;
  // 视图去回收一个已经被mCachedViews缓存的ViewHolder
  if (DEBUG && mCachedViews.contains(holder)) {
      throw new IllegalArgumentException("cached view received recycle internal? "
              + holder + exceptionLabel());
  }
  // 符合回收的条件或者该holder已经被标记为可回收。
  if (forceRecycle || holder.isRecyclable()) {
    	// 仅当mViewChacheMax > 0时(开启缓存时) 并且当前的holder并没有如下的四个标识时缓存到mCacheView当中;如果设置了mViewCacheSize = 0。那么这里就会直接跳过这部分的缓存。直接进入RecyclerViewPool。
      if (mViewCacheMax > 0
              && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
              | ViewHolder.FLAG_REMOVED
              | ViewHolder.FLAG_UPDATE
              | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
          // 淘汰掉最早缓存的视图(FIFO)
          int cachedViewSize = mCachedViews.size();
          if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
              recycleCachedViewAt(0);//将mCacheViews中第0个VH下沉到RvPool,新的需要缓存的ViewHolder存入mCacheViews
              cachedViewSize--;
          }

        	// 该变量是在mCacheViews缓存的最终的位置(position)
          int targetCacheIndex = cachedViewSize;
        	
          if (ALLOW_THREAD_GAP_WORK // Android L+
              		// mCacheViews有缓存
                  && cachedViewSize > 0
              		// 查询最后一次预取的结果中,是否包含当前holder
                  && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
              // 缓存视图时,跳过最近的预取视图
            	// 获取缓存的下标
              int cacheIndex = cachedViewSize - 1;
              while (cacheIndex >= 0) {
                	// 获取当前holder在缓存中的位置所对应的Position数值
                  int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                	// 和GapWork相关的方法,跳过最近的预取视图,如果包含cachedPos的ViewHolder,则跳过,否则break
                  if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                      break;
                  }
                  cacheIndex--;
              }
              targetCacheIndex = cacheIndex + 1;
          }
        	// 缓存进入mCachedViews
          mCachedViews.add(targetCacheIndex, holder);
        	// 标记已缓存
          cached = true;
      }
    	// 如果mCacheViews未完成缓存
      if (!cached) {
        	// 缓存进入RvPool
          addViewHolderToRecycledViewPool(holder, true);
          recycled = true;
      }
  } else {
			// 注意,一个View在它滑动动画正在运行时,不能被回收。在这种情况下,这个item最终会被ItemAnimatorRestoreListener#onAnimationFinished.所回收
    	// 考虑在ScrollBy移除项目时,取消动画,以使得ViewHolder能够快速地返回池中。
      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());
      }
  }
  // 即使holder没有被移除,我们仍然调用这个方法,以便从视图holder列表中移除它。
  mViewInfoStore.removeViewHolder(holder);
  if (!cached && !recycled && transientStatePreventsRecycling) {
      holder.mOwnerRecyclerView = null;
  }
}

五. 复用

复用的流程主要来自LayoutManger对getViewForPosition的调用,一直到tryGetViewHolderForPositionByDeadline(args),该方法的作用是尝试为指定位置申请一个ViewHolder,可能会从Recycler的Scrap、Cache和RecyclerViewPool取出或者是直接创建它

4.1 复用流程(tryGetViewHolderForPositionByDeadline()函数)

@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
    boolean dryRun, long deadlineNs) {

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

该方法中的入参有一个deadlineNs参数,如果传递的是除了FOREVER_NS之外的值,这个方法将会认为没有时间,该方法将提前返回,而不是去构建、绑定ViewHolder,LinearLayoutManager所依赖的getViewForPosition()调用时,传入的deadlineNs就是FOREVER_NS

@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    // ······
    ViewHolder holder = null;
    // 0) 如果正在预布局过程中,那么从Scrap中查找它
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        // ······
    }
    // 1) 根据位置:position 从mAttachedScrap中查找
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        if (holder != null) {
          	// 对当前ViewHolder是否可以用在指定的位置做一次检查(主要是校验ViewHolder的状态、位置是否合法,以及itemType是否和目标一致),如果不通过会回收,并且将本地变量holder重新置null
            if (!validateViewHolderForOffsetPosition(holder)) {
                // 如果校验不通过 + 不是预布局的情况下
                if (!dryRun) {
                 		// 标记该ViewHolder无效
                    holder.addFlags(ViewHolder.FLAG_INVALID);
                    // 如果是Scrap的ViewHolder,那么做一些Scrap相关的处理
                    if (holder.isScrap()) {
                      	// 移除视图
                        removeDetachedView(holder.itemView, false);
                      	// 接触scrap状态,onViewDetachedFromWindow
                        holder.unScrap();
                    } else if (holder.wasReturnedFromScrap()) {
                        holder.clearReturnedFromScrapFlag();
                    }
                    // 该视图已经不可用了,这是一个回收的逻辑,将视图失效并回收
                    recycleViewHolderInternal(holder);
                }
                holder = null;
            } else {
                fromScrapOrHiddenOrCache = true;
            }
        }
    }
  	// 2) 根据位置:id 从mAttachedScrap中查找(将会和holder.itemId进行比较)
    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) 从mAttachedScrap中根据ID获取ViewHolder
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            if (holder != null) {
                // mAttachedScrap中找到了该ViewHolder,调整ViewHolder的位置。
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true;
            }
        }
      
      	// mAttachedScrap中没找到,并且含有自定义的ViewCacheExtension,去ViewCacheExtension中找找
        if (holder == null && mViewCacheExtension != null) {
           	// 这个ViewCacheExtension#getViewForPositionAndType需要自己去重写,返回一个View
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
          	// 如果返回的View不为空,根据给出的View去检索ViewHolder,View在,对应的ViewHolder一定还在。
            if (view != null) {
              	// 根据View去检索ViewHolder
                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());
                }
            }
        }
      	// 如果ViewCacheExtension中也没有,只能取找最后一级缓存:RecyclerViewPool
        if (holder == null) { // fallback to pool
            if (DEBUG) {
                Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                        + position + ") fetching from shared pool");
            }
          	// 按照类型,从RecyclerViewPool中获取一个ViewHolder
            holder = getRecycledViewPool().getRecycledView(type);
            if (holder != null) {
                holder.resetInternal();
                if (FORCE_INVALIDATE_DISPLAY_LIST) {
                  	// SDK 18、19、20中会走这里,详细可见声明处的注释。
                    invalidateDisplayListInt(holder);
                }
            }
        }
      	// 如果RecyclerViewPool中都没有,只能尝试创建了。
        if (holder == null) {
            long start = getNanoTime();
            if (deadlineNs != FOREVER_NS
                    && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
                // 大致的意思是在这个Deadline前,无法完成任务了,直接返回null,给下一次布局。但是这个方法会保证第一次进入时的创建ViewHolder会始终生效。
                return null;
            }
          	// 创建ViewHolder
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            if (ALLOW_THREAD_GAP_WORK) {
                // 嵌套的RecyclerView的情况下
                RecyclerView innerView = findNestedRecyclerView(holder.itemView);
                if (innerView != null) {
                    holder.mNestedRecyclerView = new WeakReference<>(innerView);
                }
            }
					
            long end = getNanoTime();
          	// 重新调整上面的mRecyclerPool.willCreateInTime中的预估时间(按照总体占75%、最新的一次占25%的比例调整新的预估时间,这个预估时间会影响下一次的willCreateInTime计算。)
            mRecyclerPool.factorInCreateTime(type, end - start);
            if (DEBUG) {
                Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
            }
        }
    }
		
  	// 抓取一些数据,到目前位置,holder一定是有值的,没有值的情况下已经返回null了
    // ······
  
    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);
      	// 这句话之后,会调用到onBindViewHolder之上,于此完成ViewHolder的绑定。
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }

  	// 设置、更新LayoutParams
    final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
    final LayoutParams rvLayoutParams;
    if (lp == null) {
        rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else if (!checkLayoutParams(lp)) {
        rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else {
        rvLayoutParams = (LayoutParams) lp;
    }
    rvLayoutParams.mViewHolder = holder;
    rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
  	// 返回holder
    return holder;
}

简化后的流程如下:

LayoutManager#next();
Recycler#getViewForPosition(mCurrentPosition);
Recycler#tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs){
  	ViewHolder holder;
  	// 0) 「如果正在预布局过程中」,那么从mChangedScrap缓存中查找它,如果没有动画不必关心该方法。
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
    }
    // 1) 根据位置:position 从mAttachedScrap、Cache中查找,这种情况下,找到的基本上都是可以直接使用的,不需要做数据的rebind,hidden应该特指那种因为滑动屏幕,从RecyclerView中被detach而未被remove掉的视图,通常存在mCachceView当中。该视图对RecyclerView可见,但是对LayoutManager并不可见,这样一来,LayoutManager重新布局的时候,就不会将这些Hidden的ViewHolder布局出来。
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
      	// 对当前ViewHolder是否可以用在指定的位置做一次检查,如果不通过会回收,并且将本地变量holder重新置null。检查的项目包括:检查位置是否合法;ViewHolder是否被remove;如果有id标记类型,是否匹配。
      	// 此时还会对不可用的视图进行回收。
    }
  	// 2) 根据id 从mAttachedScrap、Cache中查找(将会和holder.itemId进行比较)
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
      	holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
    }
  	// 3) mAttachedScrap中没找到,并且含有自定义的ViewCacheExtension,去ViewCacheExtension中找找
    if (holder == null && mViewCacheExtension != null) {
      final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
      // viewHolder是存在View的layoutParams中的。
      holder = getChildViewHolder(view);
    }
    // 4) 如果ViewCacheExtension中也没有,只能取找最后一级缓存:RecyclerViewPool
    if (holder == null) { // fallback to pool
        // 按照类型,从RecyclerViewPool中获取一个ViewHolder
        holder = getRecycledViewPool().getRecycledView(type);
    }
  	if (holder == null) {
        holder = mAdapter.createViewHolder(RecyclerView.this, type);// 创建一个新的ViewHolder
    }
  
  	// 对数据进行绑定
    if (一定条件下) {
         bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
  
  	// 对itemView的LayoutParams进行进一步处理;
}

总的来说,tryGetViewHolderForPositionByDeadline()所做的工作就是,优先查找缓存,如果缓存中没有就去创建一个新的ViewHolder,这其中耦合了很多的和预布局相关的代码,分析的时候已经将预布局的代码暂时先隔离开了。

五. 总结

RecyclerView四层分级缓存的结构,实际上需要关注的可以细分成两种,上层的缓存和下层的回收。

顶层的mChangedScrapmAttachScrap更像是RecyclerView运行时必备的两个存储结构,和RecyclerView的实现原理相关,每次和动画、布局相关的操作时,RecyclerView会填充这两个缓存,布局完成后,被淘汰的页面将会进入mCachedViews中进行缓存;未被淘汰的页面,将会重新回到屏幕中显示,此时的mChangedScrap和mAttachScrap缓存都将被清空。也就是说,除了布局的任一时刻,这两个缓存中都不会含有任何VH(可以通过反射验证)。

中间的mCachedViews更像是一级真正的"缓存",它将暂时不需要用的VH存储起来,例如一些因为滑动而暂时离开屏幕的ViewHolder,如果超出其缓存大小,那么依据FIFO(先进先出)原则将最早进入的淘汰,进入RecyclerViewPool当中。

mViewCacheExtension,RecyclerView只限定了它的取,而没有限定它的存储,我们可以自行设置它的存储结构,在Adapter的某一生命周期对VH进行缓存。

底层的RecyclerViewPool,当ViewHolder走到这一层时,将会按照viewType存储。这个存储过程更像是回收,这ViewHolder已经被detach了,而不能直接搬到屏幕之上了,如果要重新使用,必须被重新attach到RecyclerView当中。

END~