目录
前言
一、Recycle 类
二、四级缓存
三、缓存详解
四、复用详解
众所周知,RecycleView 本身就是一款非常优秀的可回收、复用的原生控件,有着极高的灵活性,能够在应用中承担展示大量数据的艰巨任务。在日常开发中,使用也非常广泛。本篇将一起了解一下 RecyclerView 内部是通过怎样的缓存复用机制来实现这一功能的。
推荐阅读
(一)RecycleView 初探回收复用,onCreateView和onBindView调用关系
(二)RecycleView 实现吸附小标题的Demo(附源码)
(三)RecycleView 自定义下拉刷新,上拉加载监听
(四)RecycleView 滑动到置顶、Adapter局部刷新
(五)RecycleView 动态设置改变列表显示的高度
(六)RecycleView 回收复用机制总结
(七)RecycleView 性能提升、卡顿优化
RecycleView的特点是回收复用ViewHolder,殊不知,这一神技其实是由Recycle内部类完成的,它也是复用机制的核心部分。首先来了解一下Recycler 的主要成员变量都有哪些。
public final class Recycler {
//一级缓存
final ArrayList mAttachedScrap = new ArrayList<>();
ArrayList mChangedScrap = null;
//二级缓存
final ArrayList mCachedViews = new ArrayList();
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 和 RecyclerViewPool
recycler.recycleViewHolderInternal(viewHolder);
} else {
// 缓存到 mAttachedScrap 和 mChangedScrap
recycler.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 满了,最早加入的不要了放 RecyclerViewPool
recycleCachedViewAt(0);
}
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
// 2. 不能放进 mCacheViews 的放 RecyclerViewPool
addViewHolderToRecycledViewPool(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实例。
tryGetViewHolderForPositionByDeadLine()完整调用链流程图 :
来看看这个方法是怎么拿到相应位置上的 ViewHolder :
ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
if (mState.isPreLayout()) {
// 0) 预布局从 mChangedScrap 里面去获取 ViewHolder
holder = getChangedScrapViewForPosition(position);
}
if (holder == null) {
// 1) 分别从 mAttachedScrap、mCachedViews 获取 ViewHolder
holder = 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 = mViewCacheExtension
getViewForPositionAndType(this, position, type);
holder = getChildViewHolder(view);
}
}
if (holder == null) {
// 4) 从 RecycledViewPool 获取 ViewHolder
holder = 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调用关系
点开看看吧,文章末尾应该有答案。