RecyclerView四级缓存
四级分别挡在新建前面
列表滚动时,离开屏幕的 ViewHolder 不会马上丢掉。Recycler.tryGetViewHolderForPositionByDeadline 按固定顺序找一块能用的,四级都没有才执行 onCreateViewHolder。
- Scrap:
mAttachedScrap、mChangedScrap。没有固定格数,这次 layout 卸下多少就放多少。 - CachedViews:
mCachedViews,DEFAULT_CACHE_SIZE = 2。刚滑出屏幕的 holder 留在这里,position 和绑定都还在。 - ViewCacheExtension:默认
null,格数是 0。自己实现getViewForPositionAndType之后,这一级才有东西。 - RecycledViewPool:按
viewType分桶,DEFAULT_MAX_SCRAP = 5。每种类型最多 5 个,拿出来要重新绑定。
一块 ViewHolder 的去向
下面这块叫 A 的 ViewHolder,从还在屏幕上,到滑出屏幕,再到被同类型的新一行拿去绑定。
layout 开始时,还挂在 RecyclerView 上的 ViewHolder 被放进 mAttachedScrap。同一次 layout 又要这个 position,就从 scrap 拿回来。position 对得上、数据也没变,就不再执行 onBindViewHolder。mChangedScrap 只在预布局里用:notifyItemChanged 之后、动画开始前,变过的 holder 进这里。
A 滑出屏幕,仍然有效,就进 mCachedViews。默认只留 2 个,setItemViewCacheSize 可以改。这里还记着原来的 position 和已经绑定的数据。马上滑回来,命中的是同一格,不执行 onBindViewHolder。
屏幕上要的是一个新 position,scrap 和 CachedViews 里都没有它。Recycler 接着问 mViewCacheExtension.getViewForPositionAndType。这个扩展默认是 null,这一级什么都不返回。缓存满了之后,最旧的 holder 会被解绑送进池里,但送进池之前并不经过这一级。
A 被解绑后放进 RecycledViewPool,按 viewType 分桶,每类默认最多 5 个。进池时会走到 onViewRecycled,原来的绑定清掉。之后同类型的一项从池里拿走 A,省掉的是 onCreateViewHolder,onBindViewHolder 还是要执行。几个 RecyclerView 可以用 setRecycledViewPool 共用一个池。
四级都没有,才执行 onCreateViewHolder 造一块新的,再执行 onBindViewHolder。新建是最贵的一步,前面每一级都是在避开它。
// tryGetViewHolderForPositionByDeadline 的查找顺序
holder = attachedScrap or changedScrap;
if (holder == null) holder = cachedViews; // DEFAULT_CACHE_SIZE = 2
if (holder == null && extension != null)
view = extension.getViewForPositionAndType(recycler, position, type);
if (holder == null) holder = pool.getRecycledView(viewType); // 每类 DEFAULT_MAX_SCRAP = 5
if (holder == null) holder = adapter.createViewHolder(parent, type);
if (!holder.isBound()) adapter.onBindViewHolder(holder, position);命中之后还要不要绑定
- Scrap、CachedViews:按原来的 position 命中,数据还在,跳过
onBindViewHolder。 - RecycledViewPool:只认
viewType,不认 position。跳过的是onCreateViewHolder,绑定要重新执行。 - ViewCacheExtension:返回什么由自己决定。没有实现时,这一级等于不存在。
