1.ReCycleVIew的四级缓存
1.1.屏幕内缓存
mAttachedScrap【ArrayList】 用于保存屏幕上可见但是数据未发生变化的ViewHolder createViewHolder x bindViewHolder x
mChangedScrap【ArrayList】 用于保存屏幕上可见的ViewHolder 【当删除Item的时候触发】 createView x bindView √
1.2. 屏幕外缓存 mCacheViews 【ArrayList】 的ViewHolder,默认上限2个 createViewHolder x bindViewHolder x 【如果数据没有有变化时,不需要调用,如果已经变化则需要调用】
1.3.自定义缓存 mViewCacheExtension 不直接使用,一般需要用户自定义
1.4.缓存池 RecycledViewPool 默认上线为5个,技术上可以实现多个 RecycleView复用同一个Pool. createView x bindView √
mAttachedScrap主要用于保存屏幕上可见且数据未发生变化的ViewHolder,而mChangedScrap则用于保存数据已发生变化但仍在屏幕上的ViewHolder,需要重新绑定数据。这两种缓存机制共同作用,使得RecyclerView能够高效地管理和更新视图,特别是在数据集发生变化时
2.RecyclerView复用机制
当RecyclerView要拿一个复用的ViewHolder时:
- 如果是预加载,则会先去mChangedScrap中精准查找(分别根据position和id)对应的ViewHolder。
- 如果没有就再去mAttachedScrap和mCachedViews中精确查找(先position后id)是不是原来的ViewHolder。
- 如果还没有,则最终去mRecyclerPool找,如果itemType类型匹配对应的ViewHolder,那么返回实例,让它重新绑定数据。
- 如果mRecyclerPool也没有返回ViewHolder才会调用createViewHolder()重新去创建一个
这里有几点需要注意:
- 在mChangedScrap、mAttachedScrap、mCachedViews中拿到的ViewHolder都是精准匹配。
- mAttachedScrap和mCachedViews没有发生变化,是直接使用的。
- mChangedScrap由于发生了变化,mRecyclerPool由于数据已被抹去,所以都需要调用 onBindViewHolder()重新绑定数据才能使用。
public final class Recycler {
/**
* Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
* cache, the RecycledViewPool, or creating it directly.
*
* 尝试通过从Recycler scrap缓存、RecycledViewPool查找或直接创建的形式来获取指定位置的
ViewHolder。
* ...
*/
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
if (mState.isPreLayout()) {
// 0 尝试从mChangedScrap中获取根据postion,id获取ViewHolder对象
holder = getChangedScrapViewForPosition(position);
...
}
if (holder == null) {
// 1.1 尝试根据position从mAttachedScrap或mCachedViews中获取ViewHolder对象
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
...
}
if (holder == null) {
...
final int type = mAdapter.getItemViewType(offsetPosition);
if (mAdapter.hasStableIds()) {
// 1.2 尝试根据id从mAttachedScrap或mCachedViews中获取ViewHolder对象
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
...
}
if (holder == null && mViewCacheExtension != null) {
// 2 尝试从mViewCacheExtension中获取ViewHolder对象
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
...
}
}
if (holder == null) { // fallback to pool
// 3 尝试从mRecycledViewPool中获取ViewHolder对象
holder = getRecycledViewPool().getRecycledView(type);
...
}
if (holder == null) {
// 4.1 回调createViewHolder方法创建ViewHolder对象及其关联的视图
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
}
if (mState.isPreLayout() && holder.isBound()) {
...
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
...
// 4.1 回调bindViewHolder方法提取数据填充ViewHolder的视图内容
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
...
return holder;
}
...
}
3.RecycleView【回收机制】
(1).如果RecyclerView不滚动情况下缓存(比如删除item)、重新布局时。是
- 把屏幕上的ViewHolder与屏幕分离下来,存放到Scrap中,不发生改变的ViewHolder存放到mAttachedScrap中,如果删除一个item时,在onLayoutChild方法中,会先把当前屏幕中的ViewHolder 先缓存到mChangedScrap中,填充View的时候,会从mChangedScrap中获取。
- 剩下ViewHolder会按照mCachedViews > RecycledViewPool的优先级缓存到mCachedViews或者RecycledViewPool中。
(2).如果是RecyclerView滚动情况下缓存(比如滑动列表),在滑动时填充布局。
- 先移除滑出屏幕的item,mCachedViews优先缓存这些ViewHolder。
- 由于mCachedViews最大容量为2,当mCachedViews满了以后,会利用先进先出原则,把旧的ViewHolder存放到RecycledViewPool中后移除掉,腾出空间,再将新的ViewHolder添加到mCachedViews中。
- 最后剩下的ViewHolder都会缓存到终极回收池RecycledViewPool中,它是根据itemType来缓存不同类型的ArrayList,最大容量为5。
4.ListView二级缓存(AbsListView.RecycleBin)
4.1.mActivieViews 【View[]】 缓存屏幕内的itemView createView x bindView x 4.2.mScrapViews 【ArrayList】 缓存离开屏幕的itemView createView x bindView √
4.3.缓存读取机制如下图:
5.Recycleview缓存机制的优势 5.1.mCahceView的使用,可以做到屏幕外的itemView进入屏幕时,不需要bindView就可以快速复用。 5.2.在特定的场景下,比如ViewPager+多个列表页,可以对RecycleViewPool进行复用