RecycleView是怎么缓存的?
RecycleView中的缓存分四级缓存,每一级缓存策略都根据相应的场景设定
缓存机制
1. 存哪里?
publish final class Recycler{
//一级缓存:用来缓存还在屏幕内的 ViewHolder
ArrayList<ViewHolder> mAttachedScrap; // 存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找ViewHolder
ArrayList<ViewHolder> mChangedScrap; // 表示数据已经改变的 ViewHolder 列表, 存储 notifyXXX 方法时需要改变的ViewHolder
/**
* 二级缓存:用来缓存移除屏幕之外的ViewHolder,可通过setItemCacheSize调整,默认容量为 2
* 被滑出屏幕的ViewHolder,将会保存到这个数组中
* 但是在恢复的时候将会有两种情况:
* 1.比如说一个ViewHolder滑出后被用户马上滑回来,这个时候其实ViewHolder并没有变化,这个时候将会直接复用,不重新绑定数据
* 2.如果是新的位置需要复用Cache的时候,将会判断是否是同一个ViewType,如果是的话则复用重新绑定数据,如果不是的话,则从mRecyclerPool中获取
*/
ArrayList<ViewHolder> mCachedViews;
//三级缓存:自定义拓展View缓存,一般情况下用不上
ViewCacheExtension mViewCacheExtension;
/**
* 四级缓存:ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,就会把ViewHolder存入RecyclerViewPool中
* 1.按照 Type 来查找 ViewHolder
* 2.每个 Type 默认最多缓存 5 个
* 3.可以多个 RecyclerView 共享 RecycledViewPool
*/
RecycledViewPool mRecyclerPool;
}
2.怎么存?
举例LinearLayoutManager,在布局子控件的时候,会先暂时缓存目前所有ViewHolder,调用LayoutMananger将每个ViewHolder缓存到相应的级别
/** 调用链
* LinearLayoutManager.onLayoutChildren(...) 1.布局子控件
* -> LayoutManager.detachAndScrapAttachedViews(recycler) 2.LayoutManager缓存ViewHodler
* -> LayoutManager.scrapOrRecycleView(..., view) 3.判断每个ViewHolder显示在屏幕中
* -> Recycler.scrapView(view) 4.保存进一级缓存或二级缓存中
* or
* -> Recycler.recycleViewHolderInternal(viewHolder)
*/
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
//判断如果是失效并且没有被移除,放进二、四级缓存中
recycler.recycleViewHolderInternal(viewHolder);
} else {
//否则放进一级缓存中
recycler.scrapView(view);
}
}
//放进二、四级缓存中
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 满了,先入栈的的Holder会出栈,保存到缓存池中
recycleCachedViewAt(0);
}
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
// 2. 不能放进 mCacheViews 的放 RecyclerViewPool
addViewHolderToRecycledViewPool(holder, true);
}
}
}
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
// 标记为移除或失效的 || 完全没有改变 || item 无动画或动画不复用
mAttachedScrap.add(holder);
} else {
// 相反就得出放入 mChangedScrap 的条件啦
mChangedScrap.add(holder);
}
}
复用机制
举例LinearLayoutManager,在布局的时候,会尝试从缓存中遵循顺序去获取ViewHolder
- 首先会尝试从一、二级缓存中获取(mAttachedScrap,mChangeViews)
- 从三级缓存中获取
- 从回收池中根据ViewType获取
- 调用onCreateViewHolder创建
ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
if (mState.isPreLayout()) {
// 0) 预布局从 mChangedScrap 里面去获取 ViewHolder,动画相关
holder = getChangedScrapViewForPosition(position);
}
if (holder == null) {
// 1) 从mAttachedScrap、CachedViews 中分别尝试获取 ViewHolder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
//会对ViewHolder进行校验,比如校验ViewType是否一致等,如果不一致则继续往其他级的缓存中获取
if (!validateViewHolderForOffsetPosition(holder)) {
...
holder = null
}
}
if (holder == null) {
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) 如果 Adapter 的 hasStableIds 方法返回为 true
// 优先通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 寻找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not know it.
// 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);
}
}