环境
android sdk版本: 30
依赖:
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.recyclerview:recyclerview:1.2.1"
案例分析:
RecyclerView
宽高固定;LayoutManager
是LienarLayoutManager
,vertical方向;数据20条,足以铺满整个屏幕。
现象:
①先创建Adapter
,设置20条数据。
②调用RecyclerView#Adapter#notifyDataSetChanged
方法后,当前页面中只有5个ViewHolder
复用,其余的ViewHolder
会走Adapter#createViewHolder
方法创建新的ViewHolder
。
原理:
为了搞清楚原理,我们先看一下,刚进入页面时,RecyclerView#Adapter#onCreateViewHolder
方法的调用栈。
RecyclerView#Adapter#onCreateViewHolder方法的调用栈
RecyclerView#onLayout(): 4578行
RecyclerView#dispatchLayout(): 4012行
RecyclerView#dispatchLayoutStep2():4309行
LinearLayoutManager#onLayoutChildren(): 668行
LinearLayoutManager#fill(): 1591行
LinearLayoutManager#layoutChunk(): 1631行
LinearLayoutManager#LayoutState#next(): 2330行
RecyclerView#Recycler#getViewForPosition(int position): 6296行
RecyclerView#Recycler#getViewForPosition(position, boolean dryRun): 6300行
RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs): 6416行
RecyclerView#Adapter#createViewHolder(@NonNull ViewGroup parent, int viewType): 7295行
RecyclerView#Adapter#onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
其核心是RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline
方法,它主要有两个作用,一个是获取ViewHolder
;另一个给ViewHolder
绑定数据。
获取ViewHolder是有顺序的,会先尝试从各级缓存里面去获取,会依次从Recycler scrap
、cache
、RecycledViewPool
中获取,如果都获取不到,就直接创建一个ViewHolder
。
RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline
Attempts to get the ViewHolder for the given position, either from the Recycler scrap, cache, the RecycledViewPool, or creating it directly.
获取给定位置的ViewHolder。会依次从Recycler scrap、cache、RecycledViewPool中获取,如果都获取不到,就直接创建一个ViewHolder
核心:获取viewHolder;给viewHolder绑定数据。
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
// 如果需要预先布局,就尝试从mChangedScrap中去获取。
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
...
}
// 1) Find by position from scrap/hidden list/cache
// 尝试依次从mAttachedScrap、mChildHelper的mHiddenViews、mCachedViews中去获取可复用的ViewHolder
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
// 校验holder是否有效,无效就清除vh
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
...
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
...
// 获取这个位置对应的数据类型,通过重写的Adapter#getItemViewType方法。
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
// 如果设置了stable ids,就根据id依次从mAttachedScrap、mCachedViews中查找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
...
}
// 尝试从mViewCacheExtension中获取VH
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
...
}
}
// 尝试从 RecycledViewPool 中获取
if (holder == null) { // fallback to pool
...
holder = getRecycledViewPool().getRecycledView(type);
...
}
// 调用RecyclerView#Adapter#onCreateViewHolder生成ViewHolder
if (holder == null) {
...
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
}
...
// 给viewholder绑定数据
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
...
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
...
// 给viewholder绑定数据
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
// 给holder.itemView设置RecyclerView#LayoutParams。并将其相互绑定。
...
return holder;
}
实例分析。
我们这里调用RecyclerView#Adapter#notifyDataSetChanged
方法后,既有复用的ViewHolder
,也有新建的ViewHolder
。复用的ViewHolder
来自于哪里?为什么是5个
?为什么还要新建ViewHolder
?
带着这些问题,我们debug下我们的场景,看下ViewHolder
的来源。
核心在于调用RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline
方法,关键在于下面这段代码:
holder = getRecycledViewPool().getRecycledView(type);
我们知道,RecycledViewPool
中是以viewType
来存放不同的ViewHolder
的,每个type
最多存放五个。
所以我们在RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline
中,从RecycledViewPool
中最多能找到五个可复用的ViewHolder
,其余的只能走新建ViewHolder
流程了。
RecycledViewPool
先来看下RecycledViewPool
的说明:
RecycledViewPool lets you share Views between multiple RecyclerViews.
If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool and use setRecycledViewPool(RecyclerView.RecycledViewPool).
RecyclerView automatically creates a pool for itself if you don't provide one.
大意是RecycledViewPool
可以让你在多个recyclerview
之间共享视图。
如果你想在RecyclerViews
中回收视图,可以创建一个RecycledViewPool
的实例并使用setRecycledViewPool(RecyclerView.RecycledViewPool)
。
如果你不提供一个RecycledViewPool
实例,那么RecyclerView
会自动为自己创建一个。
我们看下RecyclerView#Recycler#getRecycledViewPool
方法:确实是自动创建了一个。
RecycledViewPool getRecycledViewPool() {
if (mRecyclerPool == null) {
mRecyclerPool = new RecycledViewPool();
}
return mRecyclerPool;
}
再看下RecycledViewPool
的结构:
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
...
}
如上所示,里面有一个SparseArray<ScrapData>
类型的变量mScrap
,用来存储不同类型的ViewHolder
。ScrapData
数据中包含ArrayList<ViewHolder>
类型变量mScrapHeap
,用来存放具体的ViewHolder
,它的最大容量是5(DEFAULT_MAX_SCRAP
)。
RecycledViewPool中的数据何时添加的
本例中RecycledViewPool
中的数据是从哪里添加的呢?
本例中,向ScrapData#mScrapHeap
添加ViewHolder
数据的调用链如下:
RecyclerView#onLayout(): 4578行
RecyclerView#dispatchLayout(): 4012行
RecyclerView#dispatchLayoutStep2():4309行
LinearLayoutManager#onLayoutChildren(): 633行
RecyclerView#LayoutManager#detachAndScrapAttachedViews(): 9493行
RecyclerView#LayoutManager#scrapOrRecycleView(): 9508行
RecyclerView#Recycler#recycleViewHolderInternal(): 6671行
RecyclerView#Recycler#addViewHolderToRecycledViewPool(): 6723行
RecyclerView#RecyclerViewPool#putRecycledView(ViewHolder scrap): 5931行
scrapHeap.add(scrap);
核心是LinearLayoutManager#onLayoutChildren()
方法中,如下的这段代码:
detachAndScrapAttachedViews(recycler);
也就是说,在调用RecyclerView#Adapter#notifyDataSetChanged
方法后,会触发绘制流程。在Linearlayout#layoutChildren
方法中,会先对ViewHolder
进行缓存,然后会对ViewHolder
进行复用。