通过博文记录RecyclerView的源码学习过程有助于巩固自己的记忆和加深整体实现机制的理解。
接《AndroidX RecyclerView总结-测量布局》,其中LinearLayoutManager在布局时,通过Recycler来获取ViewHolder中的itemView进行添加和布局。众所周知,Recycler负责缓存ViewHolder以供复用,这里通过追踪源码看看Recycler的工作机制。
文中源码基于 ‘androidx.recyclerview:recyclerview:1.1.0’
Recycler使用了多个缓存集合进行多级缓存,接下来从LinearLayoutManager的布局过程中看Recycler对ViewHolder的缓存和获取的工作流程。
先从ViewHolder存储过程入手,看看各个缓存集合的作用。
在LinearLayoutManager的布局方法onLayoutChildren中,在确定锚点之后填充布局之前,会调用detachAndScrapAttachedViews方法进行临时回收当前RecyclerView上attached的View对应的ViewHolder。
[LinearLayoutManager#onLayoutChildren]
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// ···
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
// 该方法中进行回收操作
detachAndScrapAttachedViews(recycler);
// ···
if (mAnchorInfo.mLayoutFromEnd) {
// ···
// 填充布局
fill(recycler, mLayoutState, state, false);
// ···
} else {
// ···
fill(recycler, mLayoutState, state, false);
// ···
}
// ···
}
detachAndScrapAttachedViews方法中遍历child,依次调用scrapOrRecycleView方法:
[LinearLayoutManager#scrapOrRecycleView]
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
// 获取该view对应的ViewHolder
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
// 判断是否ViewHolder的item数据被标记无效但还未从适配器数据集中移除。hasStableIds默认返回false
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
// 将触发detachedFromWindow和ViewGroup.removeViewAt方法
removeViewAt(index);
// 添加至mCachedViews或RecycledViewPool中缓存
recycler.recycleViewHolderInternal(viewHolder);
} else {
// 将为index对应的viewHolder添加FLAG_TMP_DETACHED标记,触发ViewGroup.detachViewFromParent方法
detachViewAt(index);
// 添加至mAttachedScrap或mChangedScrap中缓存
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
当重新设置Adapter或调用Adapter的notifyDataSetChanged方法会给ViewHolder标记FLAG_INVALID,需要完全重新绑定View。当移除Adapter中数据集的某个item数据时会给对应的ViewHolder标记FLAG_REMOVED,但它绑定的View可能仍然需要保留以用于item动画。同时满足以上情况时调用recycleViewHolderInternal方法进行缓存,否则调用scrapView缓存。
这里的scrap和recycle是两种不同行为。scrap表示着View仍然在RecyclerView上,只是临时detach,稍后会再attach回来。recycle意味着View将移出RecyclerView,缓存ViewHolder实例,可能不用重新绑定View,但是对应的索引位置将不一致。
recycleViewHolderInternal方法主要是对要移出RecyclerView的ViewHolder,或是item数据彻底无效或彻底移除的ViewHolder进行缓存。当RecyclerView上下滑动或item消失动画结束或彻底移除适配器数据集中对应的item等情况都会调用该方法进行回收。
[RecyclerView#recycleViewHolderInternal]
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;
// ···
// 判断是否强制回收或ViewHolder设置可回收,默认为true
if (forceRecycle || holder.isRecyclable()) {
// mViewCacheMax默认为2,判断ViewHolder是否需要重新绑定
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容量是否已满
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
// 移除第一个添加的ViewHolder,并通过addViewHolderToRecycledViewPool方法将其转移到RecycledViewPool中
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
// when adding the view, skip past most recently prefetched views
int cacheIndex = cachedViewSize - 1;
while (cacheIndex >= 0) {
int cachedPos = mCachedViews.get(cacheIndex).mPosition;
if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
cacheIndex--;
}
targetCacheIndex = cacheIndex + 1;
}
// 添加到mCachedViews中
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
// 若不满足添加进mCachedViews的条件,则添加进RecycledViewPool中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
// DEBUG···
}
// ···
}
这里将无需重新绑定View的ViewHolder保存在mCachedViews中,若mCachedViews容量不足(默认上限2),则将最早添加的转移到RecycledViewPool中。
若不满足添加进mCachedViews的条件,则将ViewHolder添加进RecycledViewPool。
在addViewHolderToRecycledViewPool方法中通过getRecycledViewPool方法获取RecycledViewPool实例:
[RecyclerView#getRecycledViewPool]
RecycledViewPool getRecycledViewPool() {
if (mRecyclerPool == null) {
mRecyclerPool = new RecycledViewPool();
}
return mRecyclerPool;
}
若mRecyclerPool已经存在,则直接返回。mRecyclerPool也可通过RecyclerView.setRecycledViewPool方法传入一个实例获得,从而支持多个RecyclerView共用一个RecycledViewPool。
这里先看一下RecycledViewPool的主要结构:
[RecycledViewPool]
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
// ···
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
// ···
}
RecycledViewPool的mScrap成员以ViewHolder的viewType为key,ScrapData为value。ScrapData中持有一个ViewHolder集合,集合的容量是5。当添加ViewHolder时,需要先取出对应viewType的ScrapData。
(可以理解为类似Map/
在取得RecycledViewPool实例后,调用它的putRecycledView方法进行添加:
[RecycledViewPool#putRecycledView]
public void putRecycledView(ViewHolder scrap) {
// 获取该ViewHolder的viewType
final int viewType = scrap.getItemViewType();
// 获取viewType对应的ViewHolder集合
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
// 判断是否达到容量上限
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
// 重置ViewHolder中的数据
scrap.resetInternal();
// 添加集合保存
scrapHeap.add(scrap);
}
RecycledViewPool的作用是一个对象缓存池,避免频繁创建ViewHolder,但是ViewHolder仍然需要重新进行View绑定。
回到scrapOrRecycleView方法中,正常布局情况下会进入Recycler的scrapView方法:
[Recycler#scrapView]
void scrapView(View view) {
// 获取该view对应的ViewHolder
final ViewHolder holder = getChildViewHolderInt(view);
// 判断存到哪个集合中
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."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
mAttachedScrap和mChangedScrap都用于缓存RecyclerView上临时detach的ViewHolder。区别是mAttachedScrap保存的是没有变化的ViewHolder,mChangedScrap保存的是有变化的,例如调用了Adapter.notifyItemRangeChanged方法。
RecyclerView触发滚动时会调用LinearLayoutManager的scrollBy方法:
[LinearLayoutManager#scrollBy]
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
// ···
mLayoutState.mRecycle = true;
// ···
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
// ···
}
该方法中先将mRecycle标记为true(默认为false,其他场景也都为false),之后通过fill方法进行填充。
在fill方法中会判断是否存在滚动,并对移除屏幕的View进行回收缓存:
[LinearLayoutManager#fill]
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// ···
// 判断是否滚动
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);
}
// ···
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
// ···
layoutChunk(recycler, state, layoutState, layoutChunkResult);
// ···
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 检查回收
recycleByLayoutState(recycler, layoutState);
}
}
// ···
}
在开始循环填充View前,先检查上下滑动的回收,在每填充一个View后也进行一次检查回收。
recycleByLayoutState方法中会判断mRecycle变量是否为false,该变量默认为true,但在前面的scrollBy中会设置为false。接着根据布局方向从顶部或底部回收ViewHolder,通过for循环逐个回收离开屏幕的View,回收的代码在removeAndRecycleViewAt方法中。
进入removeAndRecycleViewAt方法:
[LinearLayoutManager#removeAndRecycleViewAt]
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
// 从ViewGroup移除View
removeViewAt(index);
// 使用Recycler进行回收
recycler.recycleView(view);
}
在recycleView方法中会进行一些回调和清理工作,并调用recycleViewHolderInternal方法回收ViewHolder,保存进mCachedViews或RecycledViewPool中。
接着看获取缓存的工作流程,看看各缓存集合的读取优先级。LinearLayoutManager在layoutChunk方法中进行单个View的添加和布局,该方法中首先通过LayoutState的next方法获取View,而next方法中又调用Recycler的getViewForPosition方法并传入当前适配器item数据的索引:
[Recycler#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后,取里面的itemView返回。
tryGetViewHolderForPositionByDeadline方法较长,这里分成几部分看:
[Recycler#tryGetViewHolderForPositionByDeadline]
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()) {
// 如果是预布局则从mChangedScrap中查找
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
// 先从mAttachedScrap中查找,再从ChildHelper的mHiddenViews(保留用于动画的View)
// 中查找,再从mCachedViews中查找
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
// 检查ViewHolder是否无效
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();
}
// 重新回收该ViewHolder
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
// ···
}
优先mChangedScrap或mAttachedScrap中查找,找不到再从mCachedViews中查找。
[Recycler#tryGetViewHolderForPositionByDeadline]
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
// ···
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
// hasStableIds默认返回false
if (mAdapter.hasStableIds()) {
// 先从mAttachedScrap中查找,再从mCachedViews中查找
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
// 从ViewCacheExtension中查找,
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 (DEBUG) {
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的onCreateViewHolder回调创建ViewHolder
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 (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
}
// ···
}
缓存集合查找优先级:mAttachedScrap->mCachedViews->ViewCacheExtension->RecycledViewPool
当从以上缓存集合中都没有找到可用的ViewHolder后,会Adapter.createViewHolder方法进行创建。
[Adapter#createViewHolder]
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
try {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
// onCreateViewHolder由开发者实现,返回具体的ViewHolder
final VH holder = onCreateViewHolder(parent, viewType);
if (holder.itemView.getParent() != null) {
throw new IllegalStateException("ViewHolder views must not be attached when"
+ " created. Ensure that you are not passing 'true' to the attachToRoot"
+ " parameter of LayoutInflater.inflate(..., boolean attachToRoot)");
}
holder.mItemViewType = viewType;
return holder;
} finally {
TraceCompat.endSection();
}
}
触发了onCreateViewHolder回调方法返回ViewHolder。
[Recycler#tryGetViewHolderForPositionByDeadline]
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
// ···
boolean bound = false;
// 判断是否需要进行View绑定操作
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);
// 绑定View
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
// 生成RecyclerView.LayoutParams,并使之和ViewHolder互相持有引用
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;
return holder;
// ···
}
若判断ViewHolder未绑定或需要重新绑定,则调用tryBindViewHolderByDeadline方法进行绑定:
[Recycler#tryBindViewHolderByDeadline]
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
holder.mOwnerRecyclerView = RecyclerView.this;
final int viewType = holder.getItemViewType();
long startBindNs = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
// abort - we have a deadline we can't meet
return false;
}
// 调用Adapter的bindViewHolder方法进行绑定
mAdapter.bindViewHolder(holder, offsetPosition);
long endBindNs = getNanoTime();
mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
attachAccessibilityDelegateOnBind(holder);
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
return true;
}
该方法中调用Adapter的bindViewHolder方法,bindViewHolder又调用Adapter的onBindViewHolder回调方法,又开发者实现具体的绑定逻辑。
在Recycler中的缓存集合:
mAttachedScrap、mChangedScrap
填充布局前缓存当前RecyclerView上的ViewHolder,ViewHolder会短暂detach,不会remove。mAttachedScrap和mChangedScrap的区别是mChangedScrap保存需要局部刷新的ViewHolder,例如Adapter.notifyItemRangeChanged指定范围之间的。查找优先级最高。
mCachedViews
RecyclerView上下滑动或Adapter数据集变更或item移出等导致ViewHolder索引位置失效或item内容变化等数据无效的情况下缓存ViewHolder。容量上限默认是2。优先级次高。
RecycledViewPool
当往mCachedViews添加但mCachedViews超限时,会将mCachedViews里面最早添加的ViewHolder转存到RecycledViewPool中,同时重置该ViewHolder的数据。RecycledViewPool中根据viewType区分保存ViewHolder,每种viewType的默认存储上限是5。可以多个RecyclerView共用一个RecycledViewPool。优先级最低。
ViewCacheExtension
默认为空,需要开发者自行继承实现,只有获取缓存时调用,返回缓存View。优先级介于mCachedViews和RecycledViewPool之间。
当没有可用缓存时,会通过Adapter的onCreateViewHolder回调返回开发者创建的ViewHolder。在获取到ViewHolder后,判断该ViewHolder是否未绑定View(新创建的)或需要重新绑定View(数据无效、数据重置),若要绑定的话,通过Adapter的onBindViewHolder回调执行开发者实现的具体绑定逻辑。