目录
前言
一、Recycle 类
二、四级缓存
三、缓存详解
四、复用详解
前言
众所周知,RecycleView 本身就是一款非常优秀的可回收、复用的原生控件,有着极高的灵活性,能够在应用中承担展示大量数据的艰巨任务。在日常开发中,使用也非常广泛。本篇将一起了解一下 RecyclerView 内部是通过怎样的缓存复用机制来实现这一功能的。
推荐阅读
(一)RecycleView 初探回收复用,onCreateView和onBindView调用关系
(二)RecycleView 实现吸附小标题的Demo(附源码)
(三)RecycleView 自定义下拉刷新,上拉加载监听
(四)RecycleView 滑动到置顶、Adapter局部刷新
(五)RecycleView 动态设置改变列表显示的高度
(六)RecycleView 回收复用机制简单总结
(七)RecycleView 性能提升、卡顿优化
一、Recycle 类
RecycleView的特点是回收复用ViewHolder,殊不知,这一神技其实是由Recycle内部类完成的,它也是复用机制的核心部分。首先来了解一下Recycler 的主要成员变量都有哪些。
public final class Recycler {//一级缓存final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();ArrayList<ViewHolder> mChangedScrap = null;//二级缓存final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();static final int DEFAULT_CACHE_SIZE = 2;//三级缓存private ViewCacheExtension mViewCacheExtension;//四级缓存RecycledViewPool mRecyclerPool;}
Recycle类中有四个ArrayList缓存集合,它们的作用各不相同,可以按优先级从高到底分为 4个级别。
二、四级缓存
1、一级缓存:缓存屏幕内显示的ViewHolder
- mAttachedScrap 存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找 ViewHolder;
- mChangedScrap 表示数据已经改变的 ViewHolder 列表, 存储例如notify()时需要改变的 ViewHolder。
2、二级缓存:缓存少量移除屏幕之外的 ViewHolder
mCachedViews ,“一个脚踏俩只船且喜新厌旧的花心大萝卜”。
因为它默认缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则移除旧 ViewHolder。
3、三级缓存:用户自定义扩展缓存
ViewCacheExtension ,这个的创建和缓存完全由开发者自己控制,系统未往这里添加数据。
因为会脱离 Adapter.createViewHolder 进行ViewHolder的创建 ,在bindViewHolder中需要通过判断viewType做数据绑定时,不易维护。
4、四级缓存:缓存大量移除屏幕之外的ViewHolder
RecycledViewPool,“一个有底线的收容所”。
当 mCachedViews 中存不下新的 ViewHolder时,会将旧的ViewHolder存入到RecycleViewPool列表里来。但它也不是无脑接受mCachedViews的“破鞋”,它也是有底线的!它缓存限制条件就是:按照 Type 类型,默认每个Type最多缓存 5 个。另外它是用public static 修饰的,因此可以被其他RecyclerView 共享。
三、缓存详解
当调用了Adapter 的 notifyDataSetChanged方法,会调用 LayoutManager 的onLayoutChildren 方法,然后再调用LayoutManager.detachAndScrapAttachedViews(recycler)方法中进行LayoutManager.scrapOrRecycleView(..)方法中,通过针对内部不同的状态 (mFlags) 进行相应的缓存复用 ViewHolder 的处理。
//废弃或者回收利用View
private void scrapOrRecycleView(Recycler recycler, int index, View view) {final ViewHolder viewHolder = getChildViewHolderInt(view);if (viewHolder.isInvalid() && !viewHolder.isRemoved()&& !mRecyclerView.mAdapter.hasStableIds()) {// 缓存到 mCacheViews 和 RecyclerViewPoolrecycler.recycleViewHolderInternal(viewHolder);} else {// 缓存到 mAttachedScrap 和 mChangedScraprecycler.scrapView(view); }
}
1、mAttachedScrap 和 mChangedScrap
Recycle.scrapView(view)方法将屏幕上所有的 ViewHolder 回收到 mAttachedScrap 和 mChangedScrap。
void scrapView(View view) {final ViewHolder holder = getChildViewHolderInt(view);// 标记为移除或失效的 || 完全没有改变 || item 无动画或动画不复用if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {mAttachedScrap.add(holder);} else {// 相反就得出放入 mChangedScrap 的条件啦mChangedScrap.add(holder);}
}
2、mCacheViews 和 RecyclerViewPool
在scrapOrRecycleView()方法中,当ViewHolder是无效或移除时进入recycleViewHolderInternal,进行缓存。
void recycleViewHolderInternal(ViewHolder holder) {if (forceRecycle || holder.isRecyclable()) {if(mViewCacheMax > 0&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID| ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {int cachedViewSize = mCachedViews.size();if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {// 1. mCacheViews 满了,最早加入的不要了放 RecyclerViewPoolrecycleCachedViewAt(0); }mCachedViews.add(targetCacheIndex, holder);cached = true;}if (!cached) { // 2. 不能放进 mCacheViews 的放 RecyclerViewPooladdViewHolderToRecycledViewPool(holder, true);}}
}//回收缓存的视图并从列表中删除该视图
void recycleCachedViewAt(int cachedViewIndex) {ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);addViewHolderToRecycledViewPool(viewHolder, true);mCachedViews.remove(cachedViewIndex);
}
四、复用详解
复用的入口是getViewForPosition(int)方法,通过传入列表中item的位置索引,返回一个ViewHolder实例。
复用时,首先去四个缓存集合中依次寻找,是否有ViewHolder的实例,有的话就返回实例;如果都没有,才会调用createViewHolder进行创建。
public View getViewForPosition(int position) {return getViewForPosition(position, false);
}View getViewForPosition(int position, boolean dryRun) {return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {....}
大致流程:通过调用RecyclerView.getViewForPosition(int) 方法,进入到 Recycler.getViewForPosition(int, boolean)的内部方法中,最后进入到Recycler.tryGetViewHolderForPositionByDeadline()方法中,拿到ViewHolder实例。
来看看这个方法是怎么拿到相应位置上的 ViewHolder :
ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {if (mState.isPreLayout()) {// 0) 预布局从 mChangedScrap 里面去获取 ViewHolderholder = getChangedScrapViewForPosition(position);}if (holder == null) {// 1) 分别从 mAttachedScrap、mCachedViews 获取 ViewHolderholder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);}if (holder == null) {final int type = mAdapter.getItemViewType(offsetPosition);// 2) 通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 寻找if (mAdapter.hasStableIds()) {holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);}if (holder == null && mViewCacheExtension != null) {// 3) 从自定义缓存获取View view = mViewCacheExtensiongetViewForPositionAndType(this, position, type);holder = getChildViewHolder(view);}}if (holder == null) {// 4) 从 RecycledViewPool 获取 ViewHolderholder = getRecycledViewPool().getRecycledView(type);}if (holder == null) {// 缓存全取过了,没有,那只好 create 一个咯holder = mAdapter.createViewHolder(RecyclerView.this, type);}
}
1、 从 mChangedScrap 里面去寻找(前提是预布局);
2、从 mAttachedScrap和mCachedViews 寻找; (判断条件:position)
3、从 mAttachedScrap 和 mCachedViews 寻找;(判断条件:ItemId ,viewType)
4、从 mViewCacheExtension 自定义缓存寻找; (判断条件:ItemId ,viewType)
5、从 RecycledViewPool 里面去寻找;(判断条件:viewType)
6、全寻找过了,依然没有ViewHolder,就会按viewType去创建createViewHolder()一个新的。
彩蛋:
有没有人会好奇,mCachedViews 和 RecycledViewPool 这两个缓存集合,为什么要有这样俩呢?他俩内部有什么区别呢?
(一)RecycleView 初探回收复用,onCreateView和onBindView调用关系
点开看看吧,文章末尾应该有答案。