RecyclerView与ListView的缓存机制比较
RecyclerView回收机制
RecyclerView和ListView的回收机制非常相似,但是ListView是以View作为单位进行回收,RecyclerView是以ViewHolder作为单位进行回收。Recycler是RecyclerView回收机制的实现类,他实现了四级缓存:
-
mAttachedScrap: 缓存在屏幕上的ViewHolder。
-
mCachedViews: 缓存屏幕外的ViewHolder,默认为2个。ListView对于屏幕外的缓存都会调用
getView()
。 -
mViewCacheExtensions: 需要用户定制,默认不实现。
-
mRecyclerPool: 缓存池,多个RecyclerView共用。
在上文Layout Manager中已经介绍了RecyclerView的layout过程,但是一笔带过了getViewForPosition()
,因此此处介绍该方法的实现。
从上述实现可以看出,依次从mAttachedScrap, mCachedViews, mViewCacheExtension, mRecyclerPool寻找可复用的ViewHolder,如果是从mAttachedScrap或mCachedViews中获取的ViewHolder,则不会调用onBindViewHolder()
,mAttachedScrap和mCachedViews也就是我们所说的Scrap Heap;而如果从mViewCacheExtension或mRecyclerPool中获取的ViewHolder,则会调用onBindViewHolder()
。
RecyclerView局部刷新的实现原理也是基于RecyclerView的回收机制,即能直接复用的ViewHolder就不调用onBindViewHolder()
。
总结RecyclerView 缓存机制
RecyclerView 缓存机制(Recycler)大致可以分为5级。
-
第一级 通过mChangedScrap匹配 position或者id获取holder缓存。
-
第二级 从mAttachedScrap中通过匹配position获取holder缓存,或者通过ChildHelper找到隐藏但是没有被移除的View,通过getChildViewHolderInt(view)方法获取holder缓存,或者 从mCachedViews中通过匹配position获取holder缓存。
-
第三级 从mAttachedScrap中通过匹配id获取holder缓存,或者 从mCachedViews中通过匹配id获取holder缓存。
-
第四级 从ViewCacheExtension获取holder缓存。
-
第五级 通过RecyclerView 的ViewHolder缓存池获取holder。
ListView回收机制
RecycleBin缓存机制
ListView这么厉害的原因,其中一部分就是因为RecycleBin缓存机制。RecycleBin缓存机制是写在AbsListView的一个内部类。所以ListView继承于AbsListView,也继承了这股力量。让我们看看它的内部几个重要的变量和方法:
- 变量:
private View[] mActiveViews
: 缓存屏幕上可见的viewprivate int mViewTypeCount
: ListView中的子view的不同布局类型总数ArrayList<View>[] mScrapViews
: ListView中所有的废弃缓存。注意,这是一个数组。在ListView中,每种childView布局类型都会单独启用一个RecycleBin缓存机制。所以数组中的每一项ArrayList都对应着一种childView布局类型的废弃缓存。ArrayList<View> mCurrentScrap
: 当前childView布局类型下的废弃缓存。
- 方法:
void fillActiveViews ()
: 此方法会将ListView中的指定元素存储到mActiveViews数组当中。View getActiveView ()
: 从mActiveViews中获取指定的元素。取出view后,在mActiveViews里的该指定位置将被置空。所以这个mActiveViews只能使用一次,并不能复用。void addScrapView ()
: 将一个废弃的view进行缓存。如果childView的布局类型只有一项,就直接缓存到mCurrentScrap。如果多种布局,则从mScrapViews找到相对应的废弃缓存ArrayList并缓存view。View getScrapView ()
: 从废弃缓存中取出一个View。同理,如果childView的布局类型只有一项,就直接从mCurrentScrap中取。如果多种布局,则从mScrapViews找到相对应的缓存ArrayList再取出view。void setViewTypeCount ()
: 为mViewTypeCount设置childView布局类型总数,并为每种类型的childView单独启用一个RecycleBin缓存机制。
只是简单介绍了这几个重要的方法,可能大伙看得也是糊里糊涂的。或许有疑问,这个mActiveViews与mCurrentScrap有点相似,好像都是缓存view的。但它俩的区别在哪呢?莫急,等下会一一解答。
先来说说RecycleBin缓存机制的工作原理:
ListView每当一项子view滑出界面时,RecycleBin会调用addScrapView()方法将这个废弃的子view进行缓存。每当子view滑入界面时,RecycleBin会调用getScrapView()方法获取一个废弃已缓存的view。所以我们再看回Adapter的getView()
方法:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
view = LayoutInflater.from(context).inflate(resourceId, null);
······
} else {
view = convertView;
}
······
return view;
}
这个convertView是什么?convertView就是RecycleBin缓存机制调用getScrapView()方法获取废弃已缓存的view。getView()
方法中有个判断,if (convertView == null)
,当convertView为空,也就是没有废弃已缓存的view时,将调用LayoutInflater的inflate()方法加载出来布局view,这个操作是比较耗时的;当convertView不为空时,我们就直接用convertView了,而不需要再次调用LayoutInflater的inflate()方法加载出来布局view。所以如果我们没利用convertView,成千上万条数据,所有的视图都是调用LayoutInflater的inflate()方法去创建布局view的话,想想就知道这样的ListView的性能是多么的糟糕了,ListView的强大就大大打折扣了,所谓的洪荒之力也就没有了。
直接继承自AbsListView,AbsListView继承自AdapterView,AdapterView又继承自ViewGroup。
Adpater在ListView和数据源之间起到了一个桥梁的作用
RecycleBin机制
RecycleBin机制是ListView能够实现成百上千条数据都不会OOM最重要的一个原因。RecycleBin是AbsListView的一个内部类。
- RecycleBin当中使用mActiveViews这个数组来存储View,调用这个方法后就会根据传入的参数来将ListView中的指定元素存储到mActiveViews中。
- mActiveViews当中所存储的View,一旦被获取了之后就会从mActiveViews当中移除,下次获取同样位置的时候将会返回null,所以mActiveViews不能被重复利用。
- addScrapView()用于将一个废弃的View进行缓存,该方法接收一个View参数,当有某个View确定要废弃掉的时候(比如滚动出了屏幕)就应该调用这个方法来对View进行缓存,RecycleBin当中使用mScrapV
- iews和mCurrentScrap这两个List来存储废弃View。
- getScrapView 用于从废弃缓存中取出一个View,这些废弃缓存中的View是没有顺序可言的,因此getScrapView()方法中的算法也非常简单,就是直接从mCurrentScrap当中获取尾部的一个scrap view进行返回。
- 我们都知道Adapter当中可以重写一个getViewTypeCount()来表示ListView中有几种类型的数据项,而setViewTypeCount()方法的作用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。
View的流程分三步,onMeasure()用于测量View的大小,onLayout()用于确定View的布局,onDraw()用于将View绘制到界面上。
ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是”一锅端”,将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。