RecyclerView缓存复用原理
RecyclerView缓存复用机制
来到RecyclerView的Adapter代码中:
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
Log.i("minfo", "onCreateViewHolder");
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
((TextView)holder.textView).setText(datas.get(position));
Log.i("minfo", "onBindViewHolder");
}
在onCreateViewHolder方法和onBindViewHolder方法打印,不断滑动recyclerView,会发现一个现象就是刚开始都会调两个方法,但是调用一定次数之后,就只会调onBindViewHolder方法了。那么这个滑动过程中,就是在不断地回收复用。
首先要明白,RecyclerView缓存复用的是什么?缓存复用的是ViewHolder,ViewHolder是itemView的封装类。
Recycler 是 RecyclerView 的内部类,也是这套复用机制的核心,显然 Recycler 的主要成员变量也都是用来缓存和复用 ViewHolder 的:
public final class Recycler {
final ArrayList mAttachedScrap = new ArrayList<>();
ArrayList mChangedScrap = null;
final ArrayList mCachedViews = new ArrayList<>();
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
}
缓存机制
总共有四级缓存,按照优先级分:
- 一级缓存:
一级缓存的代码:
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
scrap是用来保存被rv移除掉但最近又马上要使用的缓存,比如说rv中自带item的动画效果。
稍微仔细看的话就能发现scrap缓存有两个成员mChangedScrap和mAttachedScrap,它们保存的对象有些不一样,一般调用adapter的notifyItemRangeChanged被移除的viewholder会保存到mChangedScrap,其余的notify系列方法(不包括notifyDataSetChanged)移除的viewholder会被保存到mAttachedScrap中。
二级缓存:mCachedViews ,用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 先进先出 的规则移除旧 ViewHolder,再将viewHolder添加进RecycledViewPool中。
三级缓存:ViewCacheExtension ,开发给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存。通过提供给开发者抽象方法来自己实现缓存。
四级缓存:RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,会从cachedView缓存中移除,然后将移除的 ViewHolder 存入RecyclerViewPool 中。
将缓存viewHolder添加到recyclerViewPool中,根据itemtype获取对应的ScrapData数据,里面存储着该itemType的缓存集合。RecycledViewPool默认大小为5,当大于了最大容量,就不再缓存。
可以通过以下方式修改RecycledViewPool的缓存大小:
RecyclerView.getRecycledViewPool().setMaxRecycledViews(int viewType, int max);
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
//根据itemtype获取对应的ScrapData数据
private ScrapData getScrapDataForType(int viewType) {
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}
mCachedViews二级缓存与四级缓存RecycledViewPool的区别:
cacheView缓存的viewHolder带有数据,而四级缓存已经清空了数据,只是缓存了viewHolder,从 RecyclerViewPool 中取出来的 ViewHolder 需要重新执行 bindViewHolder才能使用。所以二级缓存的复用速度会比四级缓存的复用速度更高。
绘图总结:
复用机制
在滑动过程中,recyclerView不断在复用ViewHolder来显示item,那么就从recyclerView的滑动事件入手源码,然后步步深入:
RecyclerView.onLayout(...)
-> RecyclerView.dispatchLayout()
-> RecyclerView.dispatchLayoutStep2() // do the actual layout of the views for the final state.
-> mLayout.onLayoutChildren(mRecycler, mState) // mLayout 类型为 LayoutManager
-> LinearLayoutManager.onLayoutChildren(...) // 以 LinearLayoutManager 为例
-> LinearLayoutManager.fill(...) // The magic functions :) 填充给定的布局,注释很自信的说这个方法很独立,稍微改动就能作为帮助类的一个公开方法,程序员的快乐就是这么朴实无华。
-> LinearLayoutManager.layoutChunk(recycler, layoutState) // 循环调用,每次调用填充一个 ItemView 到 RV
-> LinearLayoutManager.LayoutState.next(recycler)
-> RecyclerView.Recycler.getViewForPosition(int) // 回到主角了,通过 Recycler 获取指定位置的 ItemView
-> Recycler.getViewForPosition(int, boolean) // 调用下面方法获取 ViewHolder,并返回上面需要的 viewHolder.itemView
-> Recycler.tryGetViewHolderForPositionByDeadline(...)
最终调用 tryGetViewHolderForPositionByDeadline方法来获取ViewHolder,这个方法里面是复用的核心,方法部分代码:
ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
if (mState.isPreLayout()) {
// 从 mChangedScrap 里面去获取 ViewHolder,动画相关
holder = getChangedScrapViewForPosition(position);
}
if (holder == null) {
// mAttachedScrap、 mHiddenViews、mCachedViews 获取 ViewHolder
// 这个 mHiddenViews 是用来做动画期间的复用
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
final int type = mAdapter.getItemViewType(offsetPosition);
// 如果 Adapter 的 hasStableIds 方法返回为 true
// 优先通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 寻找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
}
if (holder == null && mViewCacheExtension != null) {
//从自定义缓存获取
View view = mViewCacheExtension
getViewForPositionAndType(this, position, type);
holder = getChildViewHolder(view);
}
}
if (holder == null) {
//从 RecycledViewPool 获取 ViewHolder
holder = getRecycledViewPool().getRecycledView(type);
}
if (holder == null) {
// 如果四级缓存里都没有,那就再调用createViewHolder创建ViewHolder
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
共有 0 条评论