RecyclerView
是Android日常开发中经常使用的控件,了解其源码,明白其中的缓存复用机制是十分有必要的;
我们都知道RecyclerView
有四级缓存,缓存的都是ViewHolder
对象,那都分别对应哪些缓存呢?各自缓存的作用是什么呢?这里先简单总结下:
层级 | 缓存变量 | 容量 | 数据结构 | 作用 |
---|---|---|---|---|
1 | mChangedScrap与 mAttachedScrap | X | ArrayList<ViewHolder> | 用来缓存还在屏幕内的ViewHolder |
2 | mCachedViews | 默认为2,可通过调用setViewCacheSize()方法调整 | ArrayList<ViewHolder> | 用来缓存移除屏幕之外的ViewHolder |
3 | mViewCacheExtension | X | 自定义缓存,一般不使用 | |
4 | mRecyclerPool | 每个itemViewType默认存储5个ViewHolder | SparseArray<ScrapData> | ViewHolder缓存池,复用时需要重新调用onBindViewHolder |
其中ScrapData
结构如下:
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
}
我们从RecyclerView
的onLayout
方法开始跟踪:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
...
dispatchLayout();
...
}
其中dispatchLayout()
方法如下:
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
//dispatchLayoutStep1()中会做以下几件事:
1.处理适配器的更新;
2.决定应该运行哪个动画;
3.保存有关当前视图的信息;
4.运行预测布局并保存其信息;
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
//dispatchLayoutStep2()中会进行实际的布局操作
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// 当宽高改变时,会再次调用 dispatchLayoutStep2()进行重新布局;
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
//dispatchLayoutStep3()处理相关动画
dispatchLayoutStep3();
}
这里我们重点关注下 dispatchLayoutStep2()
方法;
private void dispatchLayoutStep2() {
...
mLayout.onLayoutChildren(mRecycler, mState);
...
}
显然,由于dispatchLayoutStep2()
主要工作是重新布局,那么肯定要进行子View的布局;
其中 mLayout.onLayoutChildren(mRecycler, mState);
调用的是LayoutManager的onLayoutChildren
方法,
这里,我们选择LinearLayoutManager
来跟进流程;
### LinearLayoutManager.onLayoutChildren
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
detachAndScrapAttachedViews(recycler);
...
}
onLayoutChildren会调用detachAndScrapAttachedViews(recycler)方法
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
注意这里是倒序遍历,我们重点看看scrapOrRecycleView(recycler, i, v);
方法;
final ViewHolder viewHolder = getChildViewHolderInt(view);
//如果viewHolder设置成ignore,则直接返回;
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
//如果viewHolder数据非法无效 && viewHolder不指向数据集中移除的数据 && adapter没有设置stableId
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
//移除当前子View
removeViewAt(index);
//里面会调用mCachedViews和mRecyclerPool进行二级和四级缓存(三级缓存为自定义缓存)
recycler.recycleViewHolderInternal(viewHolder);
} else {
//暂时将View解绑,以便后续可以通过ViewHolder重新绑定复用
detachViewAt(index);
//里面会根据条件调用mAttachedScrap或mChangedScrap进行一级缓存;
recycler.scrapView(view);
//从消失列表中移除viewHolder
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
接下来,我们就重点分别看recycler.scrapView(view)
和 recycler.recycleViewHolderInternal(viewHolder)
方法;
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
//如果ViewHolder标记为移除或失效的 || ViewHolder没有变化 || item 无动画或动画不复用
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."));
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
从上述代码可以看出:当ViewHolder满足移除或失效||没有变化||没有动画或动画不复用时
,缓存到mAttachedScrap
集合中,否则缓存到mChangedScrap
集合中;
void recycleViewHolderInternal(ViewHolder holder) {
...
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// 先获取mCachedViews的大小
int cachedViewSize = mCachedViews.size();
//如果mCachedViews大小超过或等于默认值2的时候
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);//将下标为0位置的元素从集合中移除,放入到四级缓存mRecyclerPool中
cachedViewSize--; //集合大小-1
}
int targetCacheIndex = cachedViewSize; //将cachedViewSize赋值给targetCacheIndex
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
...
//缓存新的holder至targetCacheIndex下标中,并设置cached为true
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
//没有缓存成功,则放入到四级缓存mRecyclerPool中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
...
}
从上述代码中可以看出:当满足移除屏幕条件时:
1. 当mCachedViews没满时,ViewHolder会直接缓存到mCachedViews中,如果缓存失败,则会缓存到四级缓存mRecyclerPool中;
2. 当mCachedViews满时,会先移除mCachedViews集合中下标为0位置的元素,并将其放置到缓存池mRecyclerPool中;然后将ViewHolder缓存到mCachedViews集合下标为1位置上,如果缓存失败,则会缓存到四级缓存mRecyclerPool中;
为用户自定义缓存,可通过自定义ViewCacheExtension
,并重写getViewForPositionAndType
方法实现;
从上面二级缓存实现可以看到,会调用addViewHolderToRecycledViewPool(holder, true)
实现四级缓存机制;
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
//1.将viewHolder引用的recyclerView移除掉
clearNestedRecyclerViewIfNotNested(holder);
...
//2.移除viewHolder相关监听
if (dispatchRecycled) {
dispatchViewRecycled(holder);
}
holder.mOwnerRecyclerView = null;
//3.缓存至mRecyclerPool中;
getRecycledViewPool().putRecycledView(holder);
}
### getRecycledViewPool().putRecycledView
public void putRecycledView(ViewHolder scrap) {
//1.先获取ViewHolder对象的itemViewType
final int viewType = scrap.getItemViewType();
//2.根据itemViewType获取对应的ArrayList集合
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
//3.如果集合中已经保存有5个ViewHolder了,那就不再进行缓存操作;
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
//4.已经缓存的有,抛异常
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
//5.将ViewHolder进行`漂白`,清除相关标志、位置信息等等,因此复用缓存池中的ViewHolder需要重新进行绑定操作;
scrap.resetInternal();
//6.添加到缓冲池中;
scrapHeap.add(scrap);
}
根据ViewHolder对应的itemViewType从缓存池中获取对应的ScrapData对象,ScrapData对象内部存储了ArrayList
我们知道当RecyclerView
滑动的时候肯定会涉及到ViewHolder
的复用,因此我们就以RecyclerView
的onTouchEvent(MotionEvent e)
方法作为切入点,跟踪整个ViewHolder复用流程
;
public boolean onTouchEvent(MotionEvent e) {
//这里,我们只关注Move事件,当RecyclerView进行滑动的时候,肯定会执行ACTION_MOVE进行事件消费
case MotionEvent.ACTION_MOVE: {
...
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
e)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
...
}
} break;
}
这里重点跟下scrollByInternal()
方法;
### RecyclerView.scrollByInternal
boolean scrollByInternal(int x, int y, MotionEvent ev) {
...
if (mAdapter != null) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
//通过调用将 RV 滚动“dx”和“dy”和LayoutManager#scrollHorizontallyBy()或LayoutManager#scrollVerticallyBy关联起来
scrollStep(x, y, mReusableIntPair);
consumedX = mReusableIntPair[0];
consumedY = mReusableIntPair[1];
unconsumedX = x - consumedX;
unconsumedY = y - consumedY;
}
...
}
scrollByInternal()
中通过调用 scrollStep()
方法进行复用流程:
### RecyclerView.scrollStep
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
....
int consumedX = 0;
int consumedY = 0;
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
...
}
可以看到水平滑动dx或竖直滑动dy最终都是交给mLayout【LayoutManager】
进行处理,这里我们随便选择一个方法进去,比如scrollHorizontallyBy
### LinearLayout.scrollHorizontallyBy
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == VERTICAL) {
return 0;
}
return scrollBy(dx, recycler, state);
}
继续跟踪scrollBy()
,方法如下:
### LinearLayout.scrollBy
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
...
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
if (consumed < 0) {
if (DEBUG) {
Log.d(TAG, "Don't have any more elements to scroll");
}
return 0;
}
...
return scrolled;
}
我们重点关注下fill(recycler, mLayoutState, state, false)
### LinearLayout.fill
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen 看着像是为了修复某个bug,
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
//1.内部最终会调用RecyclerView.recycleViewHolderInternal方法进行ViewHolder缓存;
recycleByLayoutState(recycler, layoutState);
}
...
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
//2.具体复用逻辑处理;
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.endSection();
}
if (layoutChunkResult.mFinished) {
break;
}
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
//3.同1处
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
if (DEBUG) {
validateChildOrder();
}
return start - layoutState.mAvailable;
}
可以看到LinearLayout.fill
方法中会处理ViewHolder
的缓存和复用逻辑,缓存逻辑最终也是通过调用RecyclerView.recycleViewHolderInternal
方法实现,这里不再赘述,我们继续跟进复用流程layoutChunk(recycler, state, layoutState, layoutChunkResult)
;
### LinearLayout.layoutChunk
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//1.获取缓存中的View
View view = layoutState.next(recycler);
if (view == null) {
return;
}
...
//2.拿到缓存中的view根据条件去做添加重新布局
}
我们展开看下layoutState.next(recycler)
是如何实现ViewHolder复用逻辑的
;
### LinearLayout.next
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
跟进下recycler.getViewForPosition(mCurrentPosition)
### RecyclerView.getViewForPosition
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
可以看到会调用tryGetViewHolderForPositionByDeadline
方法;
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
//1.校验position是否合法,不合法抛异常
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;
// 2.如果是预布局,调用getChangedScrapViewForPosition获取缓存中的ViewHolder,内部获取的是mChangedScrap集合中的数据,也就是一级缓存
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 3.如果步骤2中获取的holder == null,调用getScrapOrHiddenOrCachedHolderForPosition获取,内部获取的是mAttachedScrap集合或mCachedViews中的数据,对应一级缓存和二级缓存;
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
//4. 检查获取到的holder是否可以用于复用【内部会通过isPreLayout、itemViewType或stableId】
if (!dryRun) {
//5.上面dryRun传入的是false,因此当获取的holder不可以复用时,会调用recycleViewHolderInternal进行回收
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;
}
}
}
//如果通过一级缓存还是获取不到holder的话
if (holder == null) {
//获取对应的itemViewType
final int type = mAdapter.getItemViewType(offsetPosition);
if (mAdapter.hasStableIds()) {
//6.如果adapter设置了StableIds,调用getScrapOrCachedViewForId方法获取缓存holder,内部获取的是mAttachedScrap集合或mCacheViews集合的数据,对应一级或二级缓存
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
...
}
if (holder == null && mViewCacheExtension != null) {
//7.如果上述仍旧没从缓存中拿到holder&&用户自定义了ViewCacheExtension(也就是三级缓存)时,尝试从三级缓存中获取View
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
...
}
}
if (holder == null) {
//8.如果上述过程仍然没拿到holder,则尝试从缓存池中获取,对应四级缓存
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
...
//9.如果所有缓存都没有获取到holder,则调用createViewHolder方法,内部会执行onCreateViewHolder创建新的ViewHolder
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
}
...
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
//如果是预布局 && holder已绑定,设置对应position
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
//10.如果holder没有绑定 || 需要更新 || 数据不合法,调用tryBindViewHolderByDeadline,内部会调用onBindViewHolder重新进行ViewHolder绑定;
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);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
//11.设置ViewHolder对应ViewGroup.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;
//12.返回从缓存中获取或者重新创建的ViewHolder对象
return holder;
}
可以看到tryGetViewHolderForPositionByDeadline
是整个VIewHolder
复用的核心逻辑,主要做了以下几件事:
校验position是否合法,不合法抛异常;
如果是预布局,调用getChangedScrapViewForPosition
获取缓存中的ViewHolder,内部获取的是mChangedScrap
集合中的数据,对应一级缓存;
如果步骤2中获取的holder == null,调用getScrapOrHiddenOrCachedHolderForPosition
获取,内部获取的是mAttachedScrap
集合或mCachedViews
集合中的数据,对应一级缓存和二级缓存;
如果本次步骤3中获取的holder!=null 但不可以复用
,则会调用recycleViewHolderInternal
进行回收,用于后续复用处理;
如果adapter设置了StableIds,调用getScrapOrCachedViewForId
方法获取缓存holder,内部获取的是mAttachedScrap集合
或mCachedViews集合
中的数据,对应一级缓存和二级缓存;
如果上述仍旧没从缓存中拿到holder&&用户自定义了ViewCacheExtension
(也就是三级缓存)时,尝试从三级缓存中获取View;
如果上述过程仍然没拿到holder,则尝试从缓存池中获取,对应四级缓存;
如果所有缓存都没有获取到holder,则调用createViewHolder方法,内部会执行onCreateViewHolder
创建新的ViewHolder;
如果是预布局 && holder已绑定,设置对应position,设置ViewGroup.LayoutParams后直接返回最终的ViewHolder;
如果holder没有绑定 || 需要更新 || 数据不合法,调用tryBindViewHolderByDeadline
,内部会调用onBindViewHolder
重新进行ViewHolder绑定,后设置ViewGroup.LayoutParams后返回最终的ViewHolder;
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )