RecyclerView内存优越性,得益于它独特的缓存机制。
1 如何复用表项
如果列表中的每个表项在移出屏幕时被销毁,移入时又被重新创建,是很消耗资源,所以RecyclerView引入了缓存机制。缓存是为了复用,复用的好处是有可能免去两个耗费资源的操作:创建表项视图和为每个表项视图绑定数据。
2 ViewHolder
2.1 作用
ViewHolder是对RecyclerView上的itemView的封装,它是RecyclerView缓存的载体。它封装了以下属性:
View itemView:对应RecyclerView的子View;int mPosition:View当前对应数据在数据源中的位置;int mOldPosition:View上次绑定的数据在数据源中的位置;long mItemId:可以判断ViewHolder是否需要重新绑定数据;int mItemViewType:itemView对应的类型;int mPreLayoutPosition:在预布局阶段ViewHolder对应数据在数据源中的位置;int mFlags:ViewHolder对应的标记位;List<Ojbect> mPayloads:实现局部刷新;Recycler mScrapContainer:如果不为空,表示ViewHolder是存放在scrap缓存中;
2.2 flag
FLAG_BOUND:ViewHolder对应的View已经绑定好了数据,无需重新绑定FLAG_UPDATE:数据发生了变化,View需要重新绑定FLAG_INVALID:数据失效了,View需要重新绑定FLAG_REMOVED:数据从数据源中删除,View在消失动画中仍然有用FLAG_NOT_RECYCLABLE:ViewHolder不能被回收,ViewHolder对应ItemView做动画时需要保证ViewHolder不能被回收掉FLAG_RETURNED_FROM_SCRAP:从scrap缓存中获取到的ViewHolderFLAG_IGNORE:如果回收该类型的ViewHolder会报错FLAG_TMP_DETACHED:表示ItemView从RecyclerView上DETACHED了,detach和remove的区别是,remove会将View从ViewGroup的children数组中删除并且刷新ViewGroup,detach只会删除不会触发刷新FLAG_ADAPTER_FULLUPDATE:表示ViewHolder需要全量更新,如果没有设置该标志位,则是局部更新FLAG_MOVED:当ViewHolder的位置发生变化,做动画时需要使用FLAG_APPEARED_IN_PRE_LAYOUT:ViewHolder出现在预布局中,需要做APPEARED动画
3 RecyclerView局部刷新
RecyclerView的数据更新,主要有以下几个方法:
notifyDataSetChanged():刷新全部可见的ViewHolder;notifyItemChanged(int position):刷新指定位置的ViewHolder;notifyItemChanged(int position, @Nullable Object payload):刷新指定位置的ViewHolder,其中playload参数可以认为是要刷新的一个标示;notifyItemRangeChanged(int positionStart, int itemCount):从指定的位置开始刷新指定个数的ViewHolder;notifyItemRangeChanged(int positionStart, int itemCount, Object payload):从指定的位置开始刷新指定个数的ViewHolder,其中playload参数可以认为是要刷新的一个标示;notifyItemInserted(int position)、notifyItemMoved(int fromPosition, int toPosition)、notifyItemRangeInserted(int positionStart, int itemCount)、notifyItemRemoved(int position)、notifyItemRangeRemoved(int positionStart, int itemCount):插入、移动、移除并自动刷新;
@Override
public void onBindViewHolder(ViewHolderholder, int position, List<Object> payloads) {if (payloads.isEmpty()) {// payloads为空,说明是更新整个ViewHolderonBindViewHolder(holder, position);} else {// payloads不为空,这只更新需要更新的View即可。String payload = payloads.get(0).toString();if ("changeColor".equals(payload)) {holder.textView.setTextColor("");}}
}
4 四级缓存
以下是RecylcerView缓存机制的时序图:

其中,Recycler用于表项的复用,RecyclerView通过Recycler获得下一个待绘制的表项。RecyclerView缓存基本上是通过三个内部类管理的,Recycler、RecycledViewPool和ViewCacheExtension,以下是RecyclerView.Recycler的部分源码:
public class RecyclerView extends ViewGroup implements ScrollingView {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;}public static class RecycledViewPool {private static final int DEFAULT_MAX_SCRAP = 5;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<>();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;}if (DEBUG && scrapHeap.contains(scrap)) {throw new IllegalArgumentException("this scrap item already exists");}scrap.resetInternal();scrapHeap.add(scrap);}}public abstract static class ViewCacheExtension {@Nullablepublic abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position, int type);}
}
scrap [skræp] 碎片,小块; attach [əˈtætʃ] 系上,贴上 detached [dɪˈtætʃt] 单独的,分离的 extension [ɪkˈstenʃn] 延伸,扩展
由上可知,Recycler有4个变量用来缓存ViewHolder对象,优先级由高到低依次为ArrayList<ViewHolder> mAttachedScrap、ArrayList<ViewHolder> mCachedViews、ViewCacheExtension mViewCacheExtension、RecycledViewPool mRecyclerPool。RecycledViewPool 对ViewHolder按viewType分类存储(通过SparseArray),同类ViewHolder存储在默认大小为5的ArrayList中。
以下是它们的含义:

RecyclerView的四级缓存:
mChangedScrap与mAttachedScrap是用来做屏幕内ViewHolder复用,不需要重新onCreateViewHolder和onBindViewHolder;mCachedView是用来缓存最近移出屏幕的ViewHolder,包括数据和position信息。复用时必须是是相同位置的ViewHolder,应用场景是在那些来回滑动的列表中,往回滑动时,能直接复用ViewHolder的数据,不用onBindeViewHolder;mViewCacheExtension,自定义缓存,这个的创建和缓存完全由开发者自己控制,系统未在这里添加数据;RecycledViewPool,ViewHolder缓存池,当mCacheView满后或者adapter被更换,将mCacheView中移出的ViewHolder放到缓存池中,同时把ViewHolder的数据清除掉,所以复用的时候需要onBindeViewHolder;

4.1 屏幕内缓存/一级缓存/scrap缓存(mAttachedScrap和mChangedScrap)
屏幕内缓存指在屏幕中显示的ViewHolder,这些ViewHolder会缓存在mAttachedScrap、mChangedScrap中:
mAttachedScrap中存放的是没有与RecyclerView分离的ViewHolder列表,从其中复用的ViewHolder既不需要重新创建也不需要重新绑定数据;mChangedScrap表示数据已经改变的ViewHolder列表,只有满足以下的条件ViewHolder才会被添加到mChangedScrap中:当它关联的itemView发生了变化(notifyItemChanged或者notifyItemRangeChanged被调用),并且ItemAnimator调用ViewHolder#canReuseUpdatedViewHolder方法时,返回了false(返回false表示要执行用一个view替换另一个view的动画,例如淡入淡出动画。true表示动画在view内部发生)。否则,ViewHolder会被添加到mAttachedScrap中;
mChangedScrap和mAttachedScrap可以看做是一个层级,都是屏幕上可见ViewHolder,只不过区分了状态(改变和未改变)。
4.2 屏幕外缓存/二级缓存(mCachedViews)
当列表滑动出了屏幕时,ViewHolder会被缓存在mCachedViews ,其大小由mViewCacheMax决定,默认DEFAULT_CACHE_SIZE为2,可通过Recyclerview.setItemViewCacheSize()动态设置。
从mCachedViews中复用的ViewHolder ,只能复用于指定位置的表项。也可以说,mCachedViews是离屏缓存,用于缓存指定位置的ViewHolder ,只有“列表回滚”这一种场景(刚滚出屏幕的表项再次进入屏幕),才有可能命中该缓存。该缓存存放在默认大小为2的ArrayList中;
4.3 自定义缓存/三级缓存(ViewCacheExtension)
可以自己实现ViewCacheExtension类实现自定义缓存,可通过Recyclerview.setViewCacheExtension()设置。
4.4 缓存池/四级缓存(RecycledViewPool)
ViewHolder在首先会缓存在mCachedViews中,当超过了个数(比如默认为2),就会添加到RecycledViewPool中。从mRecyclerPool中复用的ViewHolder,只能复用于viewType相同的表项,并且需要重新绑定数据。
RecycledViewPool会根据每个ViewType把ViewHolder分别存储在不同的列表中,每个ViewType最多缓存DEFAULT_MAX_SCRAP = 5 x ViewHolder,如果RecycledViewPool没有被多个RecycledView共享,对于线性布局,每个ViewType最多只有一个缓存,如果是网格有多少行就缓存多少个。它们之间的关系如下 :

5 缓存策略
缓存流程:
- 在插入或删除
ViewHolder的时候,首先会把屏幕内的ViewHolder保存至mAttachedScrap中; - 滑动屏幕时,首先消失的
ViewHolder会保存到mCachedViews中,mCachedView的大小默认是2,超过的话,就按照先入先出的原则,将移出头部的ViewHolder保存到RecyclerPool缓存池(如果有自定义的缓存就保存到自定义缓存中)。RecyclerPool缓存池会按照ViewHolder的viewType进行保存,每个viewType缓存个数为5个,超过了就会被回收;
获取缓存流程:首先从mAttachedScrap中获取,通过position匹配ViewHolder,如果匹配失败,就会从mCachedViews中获取,也是通过position获取ViewHolder缓存;如果获取失败,就会继续从自定义缓存中获取;如果获取失败,会继续从mRecyclerPool中获取;如果获取失败,会重新创建ViewHolder。 流程如下 :

Recyclerview在获取ViewHolder时按四级缓存的顺序查找,如果没找到就创建(createViewHolder)。其中只有RecycledViewPool找到时才会调用 onBindViewHolder,其它缓存不会重新onBindViewHolder 。
问:在RecyclerView中,滑动10个ViewHolder,再滑动回去,会有几个ViewHolder执行onBindViewHolder?
假设页面可以容纳7条数据
- 首先,页面内的
7条数据会依次调用onCreateViewHolder和onBindViewHolder; - 之后,向下滑动
1条(position = 7),会把position = 0的数据存放到mCachedViews中,此时mCachedViews缓存数量为1,RecyclerPool中缓存数量为0。position = 7的数据通过position在mCachedViews找不到对应的ViewHolder,通过viewType在mRecyclerPool中找不到对应的ViewHolder,所以会调用onCreateViewHolder和onBindViewHolder方法; - 再往下滑动
1条(position = 8),同position = 7,position = 1的数据会存放到mCachedViews; - 再往下滑动
1条(position = 9),由于mCahcedViews的缓存区默认容量为2,所以,position = 0的数据会被清空,存放到mRecyclerPool缓存池中,而position = 2的数据会存放到mCachedViews,而position = 9的数据无法通过position在mCacheViews中获取,也无法通过viewType在mRecyclerPool中获取,所以还会调用onCreateViewHolder和onBindViewHolder。此时,mCachedViews缓存区数量是2,mRecyclerPool的数量为1; - 再往下滑动
1条(position = 10),这个时候就可以通过viewType在mRecyclerPool中查找到ViewHolder,所以可以直接复用了,并通过onBindeViewHolder来绑定数据; - 依次类推
RecyclerView中,并不是每次绘制表项都会重新创建ViewHolder对象,也不是每次都会重新绑定ViewHolder数据。
- 最坏的情况:重新创建
ViewHolder并绑定数据; - 次好的情况:复用
ViewHolder但重新绑定数据; - 最好的情况:复用
ViewHolder且不重新绑定数据;
通过了解RecyclerView的四级缓存,可以知道,RecyclerView最多可以缓存N(屏幕最多可显示的item数) + 2(屏幕外的缓存) + 5 x M (M代表M个ViewType,缓存池的缓存),只有RecycledViewPool找到时才会重新调用bindViewHolder。
还需要注意的是,RecycledViewPool可以被多个RecyclerView共享,其缓存个数与ViewType个数、布局相关,如果RecycledViewPool没有被多个RecycledView共享,对于线性布局,每个ViewType最多只有一个缓存,如果是网格布局有多少行就缓存多少个。
参考
https://juejin.cn/post/6844903778303344647
https://zhooker.github.io/2017/08/14/关于Recyclerview的缓存机制的理解/
http://www.lxiaoyu.com/p/286763
https://www.jianshu.com/p/b11c62871169
https://jishuin.proginn.com/p/763bfbd55a86
https://www.jianshu.com/p/443d741c7e3e

















