前言
最近学习了RecyclerView的进阶使用,同时也学习了它的缓存机制,以下是通过看它相应的源码去了解它的获取View(ViewHolder)缓存的过程。
寻找获取缓存起点
获取缓存的起点为绘制流程的onLayout方法,然后追寻相关方法:dispatchLayout()—>dispatchLayoutStep1()—>mLayout.onLayoutChildren(mRecycler, mState)—>fill(recycler, mLayoutState, state, false)—>layoutChunk(recycler, state, layoutState, layoutChunkResult)—>layoutState.next(recycler)—>recycler.getViewForPosition(mCurrentPosition)
Recycler的getViewForPosition方法便是获取View缓存的入口,因为主要是看获取缓存的过程,所以自己也就没有去关注RecyclerView分发layout的逻辑。
获取缓存机制
获取到的其实是ViewHolder,然后从ViewHolder获取对应的View。
Recycler成员
//成员
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();//未分离
ArrayList<ViewHolder> mChangedScrap = null;//已改变
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();//缓存列表
...
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;//缓存限度
RecycledViewPool mRecyclerPool;//缓存池
private ViewCacheExtension mViewCacheExtension;//用户自定义缓存策略
获取方法:
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
......
ViewHolder holder = null;
// 1. 动画执行时调用
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 2. 通过position获取mAttachedScrap缓存中的ViewHolder,或者通过获取被隐藏但没被移除的View去获取ViewHolder
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
......
// 3.根据id和viewType去获取ViewHolder
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
......
}
//4.根据自定义获取缓存策略获取ViewHolder
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
......
}
}
if (holder == null) { // fallback to pool
......
//5. 根据viewType从缓存池中获取
holder = getRecycledViewPool().getRecycledView(type);
......//重置ViewHolder操作
}
if (holder == null) {
......
//6. 调用实现的方法去创建一个ViewHolder
holder = mAdapter.createViewHolder(RecyclerView.this, type);
......
}
}
......
return holder;
}
- getChangedScrapViewForPosition(position):有动画的时候,才会执行此方法,通过position从mChangedScrap获取ViewHolder。
- getScrapOrHiddenOrCachedHolderForPosition(position, dryRun)中的部分代码:
//1. 试着从mAttachedScrap获取
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
//2. 如果找不到合适的,则获取隐藏但未被移除的view
View view = mChildHelper.findHiddenNonRemovedView(position);
//通过View的LayoutParam获取VH
final ViewHolder vh = getChildViewHolderInt(view);
//3. 从mCacheViews获取
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
......
return holder;
}
}
- getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun)中的部分代码:
//1. 根据id和viewType匹配去获取holder从mAttachedScrap获取VH
for (int i = count - 1; i >= 0; i--) {
final ViewHolder holder = mAttachedScrap.get(i);
if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
if (type == holder.getItemViewType()) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
......
return holder;
}
......
}
}
//2. 根据id和viewType获取holder从mCachedViews获取VH
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
if (holder.getItemId() == id) {
if (type == holder.getItemViewType()) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
} else if (!dryRun) {
recycleCachedViewAt(i);
return null;
}
}
}
- mViewCacheExtension:自定义获取缓存策略,需要实现public abstract View getViewForPositionAndType(Recycler recycler, int position, int type)方法,这给我们提供了很大的灵活。通过这个方法获取View,最后再由View的LayoutParams获取ViewHolder。
- getRecycledViewPool().getRecycledView(type):通过type去获取对应的ViewHolder。
RecycledViewPool的成员如下:
//存储某个type的ViewHolder集合
static class ScrapData {
ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
//存储各个type对应的ViewHolder集合
SparseArray<ScrapData> mScrap = new SparseArray<>();
getRecycledView(type)方法:
//根据viewType获取对应的ScrapData,再从scrapHeap中获取VH并移除
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
- 到最后还是没能从缓存中获取ViewHolder,则调用需要我们实现Adapter中的createViewHolder(RecyclerView.this, type)方法。
到此从缓存中获取ViewHolder的大致流程也就走完了,下边是如何将滑出屏幕的ViewHolder缓存起来。
回收机制
入口为Recycler的recycleViewHolderInternal方法:
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满了(默认为2),则拿出一个放到RecycledViewPool中
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;
}
//将新的ViewHolder放进mCachedViews中
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
//缓存失败,直接放进RecycledViewPool,如果缓存池满了(默认每个类型为5),则不会不缓存
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
......
}
......
}
总结
- 缓存ViewHolder时,先查看mCachedViews,如果mCachedViews(默认最大为2)满了的时候,则先从中拿出一个放到RecycledViewPool(默认每个类型最大为5)中,然后再将新的放到mCachedViews中。
- 如果缓存到mCachedViews失败,则直接存放到RecycledViewPool中。
- 获取缓存时,是先复用再回收的,所以当列表第一次滑动时,mCachedViews和RecycledViewPool都还没缓存,所以需要创建新的ViewHolder。
- 当mCachedViews有缓存时,会从mCachedViews中获取,如果是当前itemView的ViewHolder,则无需创建和绑定数据。
- 当没能从mCachedViews获取到匹配的ViewHolder,则从RecycledViewPool,由于获取到的ViewHolder调用了resetInternal方法重置了成员,所以它最后会调用onBindViewHolder方法去重新绑定数据。