关于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主要针对三个场景进行回收,设立了四个回收的缓存等级:
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。
而其他的缓存结构使用的标识大多是position和itemId这两个唯一标识,它们复用时,大概率可以精确到具体的某一个最近仍然在使用的ViewHolder,实现更多视图数据的重用。
2.3 流程分析
背景:此时屏幕上含有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重新出现;
RecyclerView直接attach到屏幕上显示了;
「2」处(ViewHolder已经被回收到RecyclerViewPool中),操作同上;
RecyclerView从RecyclerViewPool中取出了ViewHolder-0,并且对它重新做了数据绑定;
三. 缓存、复用宏观流程分析
在开始介绍缓存和复用之前,我们必须知道,什么情况下RecyclerView会触发缓存和复用(什么时候存进去和什么时候取出来)。
首先,谈谈复用。复用,即将之前淘汰而未销毁的的ViewHolder取出继续使用。我们知道,RecyclerView将布局的任务,交给了LayoutManager这一控件,LayoutManager会根据自己的不同的实现,来实现线性、格栅、瀑布流等多种布局。列表控件中,LayoutManager会不断地向Recycler获取item,将RecyclerView的视图填满,这一步的实现在LayoutManager的fill()函数中,在其中调用了layoutChunk()函数。
LayoutManager会访问Recycler的相关方法(调用链最终走到tryGetViewHolderForPositionByDeadline()方法),返回一个ViewHolder,然后取它的itemView,将其返回给layoutChunk(),最终通过ChildHelper的addView方法,向RecyclerView中填充视图。这样一来,我们就知道了RecyclerView在任何一级缓存中的触发复用,一定是和tryGetViewHolderForPositionByDeadline()函数相关的。
而缓存时机,则要区分各种情况来分析,在不知全貌的情况下,最好的分析办法就是查询mAttachScrap、mChangedScrap等等结构在Recycler组件中的调用。
-
mAttachScrap和mChangedScrap,因为这俩都是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四层分级缓存的结构,实际上需要关注的可以细分成两种,上层的缓存和下层的回收。
顶层的mChangedScrap和mAttachScrap更像是RecyclerView运行时必备的两个存储结构,和RecyclerView的实现原理相关,每次和动画、布局相关的操作时,RecyclerView会填充这两个缓存,布局完成后,被淘汰的页面将会进入mCachedViews中进行缓存;未被淘汰的页面,将会重新回到屏幕中显示,此时的mChangedScrap和mAttachScrap缓存都将被清空。也就是说,除了布局的任一时刻,这两个缓存中都不会含有任何VH(可以通过反射验证)。
中间的mCachedViews更像是一级真正的"缓存",它将暂时不需要用的VH存储起来,例如一些因为滑动而暂时离开屏幕的ViewHolder,如果超出其缓存大小,那么依据FIFO(先进先出)原则将最早进入的淘汰,进入RecyclerViewPool当中。
mViewCacheExtension,RecyclerView只限定了它的取,而没有限定它的存储,我们可以自行设置它的存储结构,在Adapter的某一生命周期对VH进行缓存。
底层的RecyclerViewPool,当ViewHolder走到这一层时,将会按照viewType存储。这个存储过程更像是回收,这ViewHolder已经被detach了,而不能直接搬到屏幕之上了,如果要重新使用,必须被重新attach到RecyclerView当中。