前段时间在面试的时候这个问题被问到过,可惜自己在用的时候只知道 RecyclerView 可以通过回收和复用 view 来达到减少创建视图的优化。单内部是怎么缓存的?缓存多少?怎么区分不同的 View?
趁这个周清闲,把这个问题解决一下,源码之下无密码,那我们就从源码入手!
/**
* 使用给定的回收机制删除子视图并回收
*/
public void removeAndRecycleView(View child, Recycler recycler) {
removeView(child);
recycler.recycleView(child);
}
这个方法就是我们要研究的入口,这个方法很简单,第一行是删除子视图,第二行是回收器回收视图,我们跟进第二行的代码。
public void recycleView(View view) {
// This public recycle method tries to make view recycle-able since layout manager
// intended to recycle this view (e.g. even if it is in scrap or change cache)
ViewHolder holder = getChildViewHolderInt(view);
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
if (holder.isScrap()) {
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
这段代码主要是做了一些判断,不是我们要关注的部分,在最后一行调用了 recyclerViewHolderInternal ,我们继续跟进去。
这个方法的代码比较多,我们之截取中间一段感兴趣的代码。
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0)
int mViewCacheMax = DEFAULT_CACHE_SIZE
static final int DEFAULT_CACHE_SIZE = 2
从这里可以看到,并不是不用的 View 就会立即被回收的,而是先存储在 mCachedViews 中,当 mCachedViews 大于 2 时会回收位于第 0 个位置的视图。
/**
* 回收缓存的视图,并从列表(mCachedViews)中删除
*/
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
其中 * addViewHolderToRecycledViewPool * 方法中比较重要的是获得了 * RecycledViewPool * 对象,并向其中添加传入的 viewHolder。
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
这里再加上 scrap 的一些代码就非常好理解了
static class ScrapData {
ArrayList mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray mScrap = new SparseArray<>();
可以看到在回收 View 的时候是按 ViewType 来存储在不同的 ScrapData 中,并且最大默认缓存数是 5.
SparseArray 相当于 HashMap
View 的复用我们可以从 getViewForPosition 方法开始分析,重点在 tryGetViewHolderForPositionByDeadline 方法中,这个方法很长,我们截取重要的部分。
可以看到我们利用 RecycledViewPool 通过 viewType 获得缓存的 holder
/**
* 返回缓存并删除
*/
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList scrapHeap = scrapData.mScrapHeap;
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
View 的复用过程相对还是比较好理解的,就是通过 ViewType 来获得 RecycledViewPool 中缓存的视图。
在这次学习的过程中还注意到,可以通过给 RecyclerView 设置 RecycledViewPool 来实现多个 RecyclerView 之间的 view 共享,感觉这个挺不错。比如一些 等待View, 错误View,但 ViewType 一定要处理好。