RecyclerView源码解析之缓存机制
一、简介
RecyclerView是谷歌官方出的一个用于大量数据展示的新控件,可以代替传统的ListView,更加强大和灵活。
事实上,RecyclerView在性能上对比listView并没有显著的提示,RecyclerView更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView。
因此列表页需要支持动画,或者频繁更新或局部刷新,建议使用RecyclerView,而且通过viewType方便对item进行扩展;如果只是一个简单的列表,使用listView更加方便;
二、缓存机制
列表控件的核心无非就是看如何缓存和复用item,达到效率最大化,因此有必要从源码的角度来仔细分析一下。
分析代码之前,先看下缓存的结构(四级缓存):
源码不建议从头看,代码太多也看不完,需要找到合适的切入点,这里的切入点就是Recycler.getViewForPosition
View getViewForPosition(int position, boolean dryRun) {
//省略非关键代码
// 1) Find from scrap by position
if (holder == null) {
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
if (holder != null) {
//省略非关键代码
}
}
//此处省略非关键代码
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
//此处省略非关键代码
}
}
if (holder == null) { // fallback to recycler
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
//此处省略非关键代码
}
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
//此处省略非关键代码
return holder.itemView;
}
为了理清楚流程,删除了很多非关键代码,我们可以看到先是
getScrapViewForPosition(包含mAttachedScrap&mCacheViews)->mViewCacheExtension->mRecycledViewPool->mAdapter.createViewHolder(..),从缓存中获取viewHolder,找不到最终调用createViewHolder创建viewHolder,createViewHolder这个只是一个抽象方法,由用户自己实现。
完整流程图如下:
当item滑离屏幕的时候,会被缓存起来,这里的缓存指的是:mCacheViews和mRecycledViewPool,mCacheViews是在哪里被赋值的?很容易找到recycleViewHolderInternal方法
void recycleViewHolderInternal(ViewHolder holder) {
// 省略非关键代码
if (forceRecycle || holder.isRecyclable()) {
if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize --;
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
addViewHolderToRecycledViewPool(holder);
recycled = true;
}
} else if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ "re-visit here. We are still removing it from animation lists");
}
// ...
}
mViewCacheMax值为2,意味着mCachedViews最多缓存2个viewHolder,当超过2个的时候,会把mCachedViews.get(0)的viewHolder调用addViewHolderToRecycledViewPool接口转存到mRecycledViewPool,然后将最近移出屏幕的viewHolder存到mCachedViews中
举个例子:
屏幕上首先看到的3个item对应的viewHolder都是调用createViewHolder创建的,创建好了后调用onBindViewHolder绑定数据,这个时候我们将test0滑出屏幕
// recycleViewHolderInternal接口逻辑
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
由于最开始mCachedViews是空的,条件成立,test0对应的viewHolder加入到mCachedViews,接着将test1滑出屏幕也是加入到mCachedViews中,此时mCachedViews已经满了,再将test2滑出屏幕情况执行的是,test0会从mCachedViews中移除,转存到mRecycledViewPool中
// recycleViewHolderInternal接口逻辑
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize --;
}
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
addViewHolderToRecycledViewPool(viewHolder);
mCachedViews.remove(cachedViewIndex);
}
这个时候mRecycledViewPool存的是test0,而mCachedViews存的是test1和test2,屏幕看到的是test3、test4和test5,当test6需要得到viewHolder的时候,其实是从mRecycledViewPool中获取的test0,test0被复用。到这里,屏幕显示3个viewHolder,另外3个缓存到mCachedViews&mRecycledViewPool中,所以总共创建了6个viewHolder,也就是后续列表滑动都是这6个viewHolder循环复用
另外mViewCacheExtension是自定义的缓存这里不做讨论,而mAttachedScrap是在layout过程中被赋值的,感兴趣可以查下源码~
三、对比ListView
ListView二级缓存
ListView和RecyclerView缓存机制基本一致:
1). mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;
2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用.
3). RecyclerView的优势在于a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;b.mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势.客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。