在之前渲染过程中,layoutChunk
方法调用View view = layoutState.next(recycler)
代码,该代码获取一个view视图,内部包含缓存逻辑。RecyclerView缓存共有四级,以下简单说下四级缓存。
层级 | |
---|---|
一级缓存 | mAttachedScrap:数据没有变化的ViewHolder ; mChangedScrap:数据已经改变的 ViewHolder |
二级缓存 | mCachedViews:滑出屏幕之外的 ViewHolder |
三级缓存 | mViewCacheExtensions:自定义的缓存池 |
四级缓存 | mRecyclerPool:mCachedViews无法存放时候放置的地方,需要重新onBindViewHolder() |
其他:mHiddenViews 和Recycler无关。在子view被移出屏幕的动画执行期间暂时缓存viewHolder。
首先看下LayoutState里面的next方法。
View next(RecyclerView.Recycler recycler) {
//执行layoutForPredictiveAnimations之后mScrapList不为null
//mScrapList 实质为不可修改的mAttachedScrap
//这边忽略,一般mScrapList==null
if (mScrapList != null) {
return nextViewFromScrapList();
}
//获取一个view
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
在mScrapList == null 的情况下getViewForPosition方法进一步获取View
@NonNull
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) {
// ...省略
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
// 预加载下,通过mChangedScrap获取ViewHolder
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
// 通过mAttachedScrap、HiddenView(隐藏的view) 、mCachedViews来获取ViewHolder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
//. ..省略
}
if (holder == null) {
//...省略
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
// stableId设置为true的时候,相当于调用了viewholder中的view的requestFocus()方法。这个方法的作用是给这个请求requestFocus()的方法的view的下面的那个view焦点。
//根据ids来查找ViewHolder,设置了id,那么会忽略标志位强行复用viewHolder。
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
//...省略
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
//自定义的mViewCacheExtensions缓存获取ViewHolder
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
//...省略
}
}
if (holder == null) {
holder = getRecycledViewPool().getRecycledView(type);
//mRecyclerPool中获取ViewHolder
}
//..省略
if (holder == null) {
//..省略
holder = mAdapter.createViewHolder(RecyclerView.this, type);
//..省略
}
//..省略
}
}
//..省略
boolean bound = false;
//bindViewHolder
if (mState.isPreLayout() && holder.isBound()){
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
//..省略
return holder;
}
tryGetViewHolderForPositionByDeadline 中的代码展示完,这边展示的是一级缓存mChangedScrap、mAttachedScrap和二级mCachedViews的逻辑。以下为在一级、二级缓存中获取ViewHolder常用的代码。
ViewHolder getChangedScrapViewForPosition(int position) {
// If pre-layout, check the changed scrap for an exact match.
final int changedScrapSize;
if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
return null;
}
// find by position
for (int i = 0; i < changedScrapSize; i++) {
//通过position查找viewholder
final ViewHolder holder = mChangedScrap.get(i);
//...省略
if (mAdapter.hasStableIds()) {
//再次校验id,获取viewhodler
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
final long id = mAdapter.getItemId(offsetPosition);
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
}
return null;
}
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
//获取一个精确的,合法的scrap
final ViewHolder holder = mAttachedScrap.get(i);
//...省略
return holder;
}
}
if (!dryRun) {
View view = mChildHelper.findHiddenNonRemovedView(position);
if (view != null) {
// This View is good to be used. We just need to unhide, detach and move to the
// scrap list.
//隐藏但未被移除的view
final ViewHolder vh = getChildViewHolderInt(view);
//...省略
return vh;
}
}
// Search in our first-level recycled view cache.
//缓存获取
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
// invalid view holders may be in cache if adapter has stable ids as they can be
// retrieved via getScrapOrCachedViewForId
if (!holder.isInvalid() && holder.getLayoutPosition() == position
&& !holder.isAttachedToTransitionOverlay()) {
if (!dryRun) {
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
return holder;
}
}
return null;
}
//Adapter的notifyDataSetChanged会令所有viewHolder为FLAG_UPDATE和FLAG_INVALID
//设置id,会忽略标志位置,强行获取ViewHolder。确保不多次创建view。
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
// Look in our attached views first
final int count = mAttachedScrap.size();
for (int i = count - 1; i >= 0; i--) {
final ViewHolder holder = mAttachedScrap.get(i);
//根据id来查找ViewHolder
if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
if (type == holder.getItemViewType()) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
if (holder.isRemoved()) {
// this might be valid in two cases:
// > item is removed but we are in pre-layout pass
// >> do nothing. return as is. make sure we don't rebind
// > item is removed then added to another position and we are in
// post layout.
// >> remove removed and invalid flags, add update flag to rebind
// because item was invisible to us and we don't know what happened in
// between.
if (!mState.isPreLayout()) {
holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
}
}
return holder;
} else if (!dryRun) {
// if we are running animations, it is actually better to keep it in scrap
// but this would force layout manager to lay it out which would be bad.
// Recycle this scrap. Type mismatch.
mAttachedScrap.remove(i);
removeDetachedView(holder.itemView, false);
quickRecycleScrapView(holder.itemView);
}
}
}
// Search the first-level cache
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
if (holder.getItemId() == id && !holder.isAttachedToTransitionOverlay()) {
if (type == holder.getItemViewType()) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
} else if (!dryRun) {
recycleCachedViewAt(i);
return null;
}
}
}
return null;
}
以上完成了ViewHolder的创建,接下来查看各个缓存的生成。
- mChangedScrap 和 mAttachedScrap
以下代码会将变化的viewholder存入到mChangedScrap、不变的viewholder存入到mAttachedScrap
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//..省略
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);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
- mCachedViews
当item被滑出屏幕后会被保存在mCachedViews中,在dispatchLayoutStep1方法中调用
processAdapterUpdatesAndSetAnimationFlags时候会坚持mCachedViews,如果mCachedViews有变化,则会mCachedViews中移除掉ViewHolder。
void recycleViewHolderInternal(ViewHolder holder) {
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();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
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.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
- mRecyclerPool
当mCachedViews被填满溢出的viewholder,会被移动到RecyclerRool中。 - mHiddenViews 和 mUnmodifiableAttachedScrap :mHiddenViews当view被移除屏幕又要执行动画,所有会暂存到mHiddenViews中。mUnmodifiableAttachedScrap实为不可修改的mAttachedScrap,在执行动画区间会暂时充当mAttachedScrap中的一项,只有动画只想完成后会删除mAttachedScrap中的viewholder。