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

环境

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(): 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 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(): 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进行复用。

相关资料

demo-AdapterOnCreateViewHolderTestActivity

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tinyvampirepudge

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值