「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」
我们理解RecyclerView的缓存的话,可以从三个问题入手:
- 缓存是什么?复用是什么
- 缓存到哪里去了?又从哪里获得的复用
- 什么时候缓存?什么时候复用
一、 ViewHolder
1.为什么要使用ViewHolder
ItemView 和 ViewHolder 的关系是一对一,即一个 ViewHolder 对应一个 ItemView。RecyclerView的缓存机制,其实就是ViewHolder
RecyclerView的缓存分为四级:
- mChangeScrap 和 mAttachedScrap (屏幕内的缓存数据,mChangeScrap表示数据已经改变的ViewHolder列表,mAttachedScrap表示数据已经改变的ViewHolder列表)
- mCachedViews (刚刚移除屏幕的缓存数据)
- mViewCacheExtension (开发者自定义缓存,需要自己管理View的创建和缓存)
- RecycledViewPool (ViewHolder的缓存池)
二、Recycler
Recycler用于管理已经废弃或与RecyclerView分离的ViewHolder。ViewHolder的缓存是由屏幕内的缓存数据mChangeScrap、 mAttachedScrap 和 刚刚移除屏幕的缓存mCachedViews组成的。RecycledViewPool是用来缓存整体的ViewHolder,当mCachedViews缓存满了,会把先缓存进去的ViewHolder移除并缓存到RecycledViewPool
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;
}
当ViewHolder被创建后,就要被缓存,然后复用,那是什么时候添加到缓存中的呢?
- 当ViewHolder无效,并且被remove,执行recycler.recycleViewHolderInternal(viewHolder)方法,将ViewHolder添加到CacheView和Pool中
- 当deatch时,将View加入到scrapView()中
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
. . .
//ViewHolder无效,并且已经被移除
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
我们再看看scrapView()的具体逻辑
- 获取对应的ViewHolder
- 判断该ViewHolder是否分离,如果为分离添加到mAttachedScrap
- 如果已经分离添加到mChangedScrap
void scrapView(View view) {
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()) {
...
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
如果是已经在屏幕外的缓存呢?
屏幕外的缓存,我们认为是二级缓存,使用的是mCachedViews,缓存复用时需要匹配position,这时集合里面的ViewHolder信息还在,不需要重新onBindViewHolder,直接添加到Recycler显示,它的默认容量是2
如果存满的话怎么办呢?
如果存满的话,就会移除最后一个缓存,加到RecycledViewPool中
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
...
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
我们再来看看RecycledViewPool
RecycledViewPool的默认容量的5
private static final int DEFAULT_MAX_SCRAP = 5;
RecycledViewPool保存逻辑为:
- 获取ViewHolder布局类型
- 根据布局类型获取ViewHolder
- 判断缓存池大小,默认5个
- 同一种viewtype只保存5个ViewHolder
- 清空ViewHolder记录
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
这篇主要讲的如何添加到缓存,下一篇主要讲如果从缓存取出来