看到标题说的是三级缓存,有的地方说是四级缓存,请你不要迷惑,到底是三还是四,这就像图片加载这个场景有人说是三级缓存有人说是二级缓存,说三级缓存是把通过网络请求图片这个环节也认为是一层缓存,你认为这个环节应该不应该属于缓存呢?所以到底是三还是四不重要,因为逻辑是固定的.
其实如果要比较Recyclerview
和ScrollView
这两个哪个控件使用起来更简单,那必然是ScrollView
,而且上手难度完全不是一个量级的.那为什么ScrollView
的出镜率完全比不上Recyclerview
呢?这就要归功于Recyclerview
缓存设计和他的可扩展性了.
Recyclerview源码分析:二、滑动时如何布局中有提到即使你有再多的childView
需要展示,但是Recyclerview
只会创造一定数量的childView
,那我们有以下几个问题需要探索一下:
前提:垂直布局的Recyclerview高度为100dp,所有的child高度为10dp,初始化时填充了10个child,这时候手指向上滑动了30dp,然后再向下滑30dp回到默认的位置
- 在向上滑动30dp的这个环节中,划出屏幕中的
child0
、child1
、child2
这三个child是否进入到同一个缓存中了? - 在向上滑动的30dp的这个环境中,从屏幕底部滑进来的
child10
、child11
、child12
这三个child都是全新创建的ViewHolder
吗?如果不是哪些是用的缓存,哪些是全新创建的? - 在向下滑动30dp回到默认位置的这个环节中,滑进来的的
child2
、child1
、child0
这三个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
会被移入其他缓存层,真正的移入其他缓存层逻辑被交给了RecycledViewPool
的putRecycledView()
.
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源码分析:二、滑动时布局是如何填充的
两篇讨论如何填充和布局的时候,在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