IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> RecyclerView#Adapter#notifyDataSetChanged方法后,为何还会新建ViewHolder? -> 正文阅读

[移动开发]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

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-12 17:41:04  更:2022-03-12 17:41:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年10日历 -2024/10/31 19:23:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码