看到标题说的是三级缓存,有的地方说是四级缓存,请你不要迷惑,到底是三仍是四,这就像图片加载这个场景有人说是三级缓存有人说是二级缓存,说三级缓存是把经过网络恳求图片这个环节也以为是一层缓存,你以为这个环节应该不应该属于缓存呢?所以到底是三仍是四不重要,由于逻辑是固定的.
其实假如要比较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源码剖析:一、静态时怎么布局的
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