前面我们在讲RecyclerView回收机制的时候已经提及了它复用机制。但是并没有沿着复用分支继续向下研究,这里将补齐复用分支的源码分析,让我们来看一下RecyclerView是如何实现VH复用的。
在讲回收机制的时候我们说回收机制有两个入口onTouch和onLayout。相应的复用同样也是两个入口。但是两个入口的复用逻辑都是从LinearLayoutManager.layoutChunk()开始。所以这里我们就只需要挑选onTouch一条线分析就可以了。
这里还是要提一下RecyclerView的四级缓存
| 缓存结构 | 说明 |
一级缓存 | mChangedScrap 与 mAttachedScrap | onLayout过程中屏幕内或正在移出屏幕的Item |
二级缓存 | mCachedViews | 刚刚移除屏幕的Item,默认大小2 |
三级缓存 | mViewCacheExtension | Google留给开发者自定义的缓存 |
四级缓存 | mRecyclerPool | 当mCachedViews缓存满后会根据FIFO的规则将二级缓存中移出的ViewHolder缓存到RecycledViewPool中,默认大小5 |
复用逻辑入口:Recycler. tryGetViewHolderForPositionByDeadline
ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
.....
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
1、第一次尝试复用:如果有改变的表项则从mChangedScrap中查找VH
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
2、第二次尝试复用:通过position位置在mAttachedScrap和mCachedViews中查找VH
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
......
}
if (holder == null) {
....
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {//如果存在StableIds
3、第三次尝试复用:通过StableIds在mAttachedScrap和mCachedViews中查找
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
....
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
4、第四次尝试复用:如果存在自定义缓存,则通过自定义缓存查询View,再通过View获取与其绑定的VH
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
.....
}
}
if (holder == null) { // fallback to pool
....
5、第五次尝试复用:通过回收池RecyclerViewPool查找VH
holder = getRecycledViewPool().getRecycledView(type);
....
}
if (holder == null) {
.....
6、创建新VH:如果四级缓存中都没有查找到合适的VH,则创建新的VH
holder = mAdapter.createViewHolder(RecyclerView.this, type);
....
}
}
......
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()) {
......
7、绑定VH:VH未绑定或者需要更新或者无效状态时,重新绑定VH
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
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;//将VH和View建立绑定关系
rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
return holder;
}
1、第一次尝试复用
通过StableIds在mAttachedScrap和mCachedViews中查找VH
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++) {
final ViewHolder holder = mChangedScrap.get(i);
查找mChangedScrap并匹配position
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// find by id
if (mAdapter.hasStableIds()) {
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);
//查找mChangedScrap,并匹配ItemId
if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
}
return null;
}
2、第二次尝试复用
通过position位置在mAttachedScrap和mCachedViews中查找VH
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++) {
final ViewHolder holder = mAttachedScrap.get(i);
//查找mAttachedScrap并匹配position,且VH有效未移除
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
.....
// 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);
//查找mCachedViews并匹配position,且VH有效
// 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) {
if (!dryRun) {
mCachedViews.remove(i);
}
....
return holder;
}
}
return null;
}
3、第三次尝试复用
通过StableIds在mAttachedScrap和mCachedViews中查找VH
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);
//查找mAttachedScrap,并匹配ItemId
if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
if (type == holder.getItemViewType()) {//匹配ViewType
.....
return holder;
} else if (!dryRun) {
//如果View正在执行动画等,则从mAttachedScrap中移除
// 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);
//查找mCachedViews,并匹配ItemId
if (holder.getItemId() == id) {
if (type == holder.getItemViewType()) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
} else if (!dryRun) {
recycleCachedViewAt(i);
return null;
}
}
}
return null;
}
4、第四次尝试复用
如果存在自定义缓存,则通过自定义缓存查询View,再通过View获取与其绑定的VH
ViewCacheExtension 是一个抽象接口,需要开发者自定义并通过RecyclerView设置。
public abstract static class ViewCacheExtension {
/**
* Returns a View that can be binded to the given Adapter position.
* 通过自定义的缓存返回对应position和type的View
*/
@Nullable
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
int type);
}
5、第五次尝试复用
通过回收池RecyclerViewPool查找VH
RecyclerViewPool的数据结构有点像Map,Key/Value类型的数据结构,不过Key值的类型是固定的int型,对应着ViewType。如下:
public ViewHolder getRecycledView(int viewType) {
//通过ViewType匹配到对应的Value值ScrapData。
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
//取出最新存入的VH,并从缓存的List中移除
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
6、创建新VH
如果四级缓存中都没有查找到合适的VH,则创建新的VH
有人可能会说这里有什么好讲的,不就是调用Adapter的createViewHolder方法创建新的VH吗。确实,这一块是没什么好讲的 ,但是我想讲的并不是createViewHolder这个方法,而是createViewHolder之后调用另外一个方法mRecyclerPool.factorInCreateTime(type, end - start)。这个方法是干嘛的呢?我们来看看:
void factorInCreateTime(int viewType, long createTimeNs) {
ScrapData scrapData = getScrapDataForType(viewType);//通过ViewType查找RecycledViewPool中对应的VH列表
scrapData.mCreateRunningAverageNs = runningAverage(
scrapData.mCreateRunningAverageNs, createTimeNs);//更新列表创建时间
}
private ScrapData getScrapDataForType(int viewType) {
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {//未查找到ViewType对应的VH列表,则未该ViewType创建对应的VH列表。每个列表的默认大小为5
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}
7、绑定VH
VH未绑定或者需要更新或者无效状态时,重新绑定VH
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;
}
mAdapter.bindViewHolder(holder, offsetPosition);//调用Adapter的bindViewHolder方法
long endBindNs = getNanoTime();
mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);//更新RecycledViewPool中ViewType对应的VH列表绑定时间
attachAccessibilityDelegateOnBind(holder);
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
return true;
}
至此,RecyclerView的缓存机制我们的粗略讲完了。如果还是有人觉得一脸懵逼,我建议对着流程图自己去抠几遍源码!