RecyclerView#Adapter#notifyDataSetChanged方法后,为何还会新建ViewHolder?

883 阅读4分钟

环境

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宽高固定;LayoutManagerLienarLayoutManager,vertical方向;数据20条,足以铺满整个屏幕。

现象:

①先创建Adapter,设置20条数据。

②调用RecyclerView#Adapter#notifyDataSetChanged方法后,当前页面中只有5个ViewHolder复用,其余的ViewHolder会走Adapter#createViewHolder方法创建新的ViewHolder

原理:

为了搞清楚原理,我们先看一下,刚进入页面时,RecyclerView#Adapter#onCreateViewHolder方法的调用栈。

RecyclerView#Adapter#onCreateViewHolder方法的调用栈


RecyclerView#onLayout(): 4578RecyclerView#dispatchLayout(): 4012RecyclerView#dispatchLayoutStep2():4309LinearLayoutManager#onLayoutChildren(): 668LinearLayoutManager#fill(): 1591LinearLayoutManager#layoutChunk(): 1631LinearLayoutManager#LayoutState#next(): 2330RecyclerView#Recycler#getViewForPosition(int position): 6296RecyclerView#Recycler#getViewForPosition(position, boolean dryRun): 6300RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs): 6416RecyclerView#Adapter#createViewHolder(@NonNull ViewGroup parent, int viewType): 7295RecyclerView#Adapter#onCreateViewHolder(@NonNull ViewGroup parent, int viewType)

其核心是RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline方法,它主要有两个作用,一个是获取ViewHolder;另一个给ViewHolder绑定数据。

获取ViewHolder是有顺序的,会先尝试从各级缓存里面去获取,会依次从Recycler scrapcacheRecycledViewPool中获取,如果都获取不到,就直接创建一个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,用来存储不同类型的ViewHolderScrapData数据中包含ArrayList<ViewHolder>类型变量mScrapHeap,用来存放具体的ViewHolder,它的最大容量是5(DEFAULT_MAX_SCRAP)。

RecycledViewPool中的数据何时添加的

本例中RecycledViewPool中的数据是从哪里添加的呢?

本例中,向ScrapData#mScrapHeap添加ViewHolder数据的调用链如下:


RecyclerView#onLayout(): 4578RecyclerView#dispatchLayout(): 4012RecyclerView#dispatchLayoutStep2():4309LinearLayoutManager#onLayoutChildren(): 633RecyclerView#LayoutManager#detachAndScrapAttachedViews(): 9493RecyclerView#LayoutManager#scrapOrRecycleView(): 9508RecyclerView#Recycler#recycleViewHolderInternal(): 6671RecyclerView#Recycler#addViewHolderToRecycledViewPool(): 6723RecyclerView#RecyclerViewPool#putRecycledView(ViewHolder scrap): 5931scrapHeap.add(scrap);

核心是LinearLayoutManager#onLayoutChildren()方法中,如下的这段代码:


detachAndScrapAttachedViews(recycler);

也就是说,在调用RecyclerView#Adapter#notifyDataSetChanged方法后,会触发绘制流程。在Linearlayout#layoutChildren方法中,会先对ViewHolder进行缓存,然后会对ViewHolder进行复用。

相关资料

demo-AdapterOnCreateViewHolderTestActivity