1、一级缓存 mAttachedScrap:
缓存屏幕中可见范围的ViewHolder。他还可以保存item自带的动画效果,因为这些viewholer数据上是没有改变的,只是位置改变而已,所以放置到scrap最为合适。
final ArrayList
2、二级缓存 mCachedViews:
缓存滑动时即将与RecyclerView分离的ViewHolder,按子View的position或id缓存,默认最多存放2个。
final ArrayList
static final int DEFAULT_CACHE_SIZE = 2;
3、三级缓存 mViewCacheExtension :
开发者自行实现的缓存,扩展缓存,也可能是谷歌自留的。
4、四级缓存 mRecyclerPool:
ViewHolder缓存池,本质上是一个SparseArray,其中key是ViewType(int类型),value存放的是 ArrayList< ViewHolder>,默认每个ArrayList中最多存放5个ViewHolder。是通过item的type来找holder的,找到之后需要重新绑定数据。
holder = getRecycledViewPool().getRecycledView(type);
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
for (int i = scrapHeap.size() - 1; i >= 0; i--) {
if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
//获取到holder后返回,并从集合中移除这个holder。
return scrapHeap.remove(i);
}
}
}
return null;
}
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
getViewForPosition()即是是从RecyclerView的回收机制实现类Recycler中获取合适的View
@NonNull
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
//根据传入的position获取ViewHolder
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
---------省略----------
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
//预布局 属于特殊情况 从mChangedScrap中获取ViewHolder
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
if (holder == null) {
//1、尝试从mAttachedScrap中获取ViewHolder,此时获取的是屏幕中可见范围中的ViewHolder
//2、mAttachedScrap缓存中没有的话,继续从mCachedViews尝试获取ViewHolder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
----------省略----------
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
---------省略----------
final int type = mAdapter.getItemViewType(offsetPosition);
//如果Adapter中声明了Id,尝试从id中获取,这里不属于缓存
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
}
if (holder == null && mViewCacheExtension != null) {
3、从自定义缓存mViewCacheExtension中尝试获取ViewHolder,该缓存需要开发者实现
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}
if (holder == null) { // fallback to pool
//4、从缓存池mRecyclerPool中尝试获取ViewHolder
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
//如果获取成功,会重置ViewHolder状态,所以需要重新执行Adapter#onBindViewHolder绑定数据
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
---------省略----------
//5、若以上缓存中都没有找到对应的ViewHolder,最终会调用Adapter中的onCreateViewHolder创建一个
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//6、如果需要绑定数据,会调用Adapter#onBindViewHolder来绑定数据
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
----------省略----------
return holder;
}
总结一下上述流程:通过mAttachedScrap、mCachedViews及mViewCacheExtension获取的ViewHolder不需要重新创建布局及绑定数据;通过缓存池mRecyclerPool获取的ViewHolder不需要重新创建布局,但是需要重新绑定数据;如果上述缓存中都没有获取到目标ViewHolder,那么就会回调Adapter#onCreateViewHolder创建布局,以及回调Adapter#onBindViewHolder来绑定数据。
再说说自定义缓存
Recycler本身已经设置了好几级缓存了,为什么还要留个接口让开发者去自行实现缓存呢?关于这一点,谈一谈我的理解:来看看Recycler中的其他缓存,其中mAttachedScrap用来处理可见屏幕的缓存;mCachedViews里存储的数据虽然是根据position来缓存,但是里面的数据随时可能会被替换的;再来看mRecyclerPool,mRecyclerPool里按viewType去存储ArrayList< ViewHolder>,所以mRecyclerPool并不能按position去存储ViewHolder,而且从mRecyclerPool取出的View每次都要去走Adapter#onBindViewHolder去重新绑定数据。假如我现在需要在一个特定的位置(比如position=0位置)一直展示某个View,且里面的内容是不变的,那么最好的情况就是在特定位置时,既不需要每次重新创建View,也不需要每次都去重新绑定数据,上面的几种缓存显然都是不适用的,这种情况该怎么办呢?可以通过自定义缓存ViewCacheExtension实现上述需求。
ViewCacheExtension适用场景:ViewHolder位置固定、内容固定、数量有限时使用。
比如在position=0时展示的是一个广告,位置不变,内容不变,来看看如何实现. 具体看 https://www.jianshu.com/p/e1b257484961
ListView和RecycleView
ListView和RecyclerView缓存机制基本一致:
ListView只有两级缓存 mActiveViews和mScrapView。
1). mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;
2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用。
lietView使用mScrapView缓存是不用创建view,但是需重新绑定数据的。getView方法中会从convertView中判断有没有view。没有再创建新的,有的话复用该view。
3). RecyclerView的优势在于a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;b.mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势.客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善
。