Android从源码分析RecyclerView四级缓存复用机制一(缓存ViewHolder)

2023-11-13

RecyclerView相比较ListView先说多了多布局和缓存,目前已经在Android列表中大量普及使用,面试中也经常问到,所以对于RecyclerView的四级缓存机制也叫复用回收机制的分析很有必要(这部分很重要请@全村人来听)。
在这里插入图片描述

先说一下结论RecyclerView的四级缓存分别为:

  1. mChangeScrap与 mAttachedScrap 用来缓存还在屏幕内的 ViewHolder
  2. mCachedViews 用来缓存移除屏幕之外的 ViewHolder
  3. mViewCacheExtension 开发给用户的自定义扩展缓存,需要用户自己 管理 View 的创建和缓存
  4. RecycledViewPool ViewHolder 缓存池

本文将从从以下问题中逐步分析源码并寻找答案并验证结论:

1.RecyclerView回收(缓存)什么?复用什么?

答:回收和复用的都是ViewHolder,RecyclerView 的复用的主要方法tryGetViewHolderForPositionByDeadline()返回的是ViewHolder。同时几个回收(缓存)的主要方法返回的也是ViewHolder。

2.RecyclerView回收(缓存)到哪里去?从哪里获得复用?

1.先来看一下ViewHolder最终缓存地方RecyclerView.Recycler类中,本文的源码分析基于最新的AndroidX的RecyclerView(和以前的RecyclerView可能有点不一样,但是主要流程都是一致的)

 public final class Recycler {
        //一级缓存中用来存储屏幕中显示的ViewHolder
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        ArrayList<ViewHolder> mChangedScrap = null;
        //二级缓存中用来存储屏幕外的ViewHolder
        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
        //暂可忽略 mAttachedScrap的不可变视图
        private final List<ViewHolder>
                mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
        
        private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
        //mCachedViews屏幕外缓存的存储上限默认为DEFAULT_CACHE_SIZE也就是2,可变
        int mViewCacheMax = DEFAULT_CACHE_SIZE;
        //四级缓存当屏幕外缓存的大小大于2,便放入mRecyclerPool中缓存
        RecycledViewPool mRecyclerPool;
        //三级缓存自定义缓存,自己定义的缓存规则
        private ViewCacheExtension mViewCacheExtension;
        //默认屏幕外缓存大小
        static final int DEFAULT_CACHE_SIZE = 2;
        //...

2.再看一下主要的方法调用流程,从RecyclerView的onMeasure方法开始一直到三个存储的地方一级,二级和四级缓存,别问为啥没有mViewCacheExtension,问就是这个你需要自己去存
在这里插入图片描述

3.源码分析,本文对重点方法和重点代码进行分析(流程最好自己去跟一下)。

/**
* 5.RecyclerView.scrapOrRecycleView
*/
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
   final ViewHolder viewHolder = getChildViewHolderInt(view);
      //只展示重点代码...
      if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                    && !mRecyclerView.mAdapter.hasStableIds()) {
         removeViewAt(index);
         //这里调用到二级和四级缓存
         recycler.recycleViewHolderInternal(viewHolder);
      } else {
         detachViewAt(index);
         //这里调用到一级缓存
         recycler.scrapView(view);
         mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
      }
}

① 分析一级缓存

/**
* 13.RecyclerView.scrapView
*/
void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                    || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                //...
                holder.setScrapContainer(this, false);
                //缓存adapter其他notify系列方法(包括notifyDataSetChanged)被移除的ViewHolder
                mAttachedScrap.add(holder);
            } else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                holder.setScrapContainer(this, true);
                //缓存adapter的notifyItemRangeChanged被移除的ViewHolder
                mChangedScrap.add(holder);
            }
        }

② 分析二级缓存

/**
* 6.RecyclerView.recycleViewHolderInternal
*/
void recycleViewHolderInternal(ViewHolder holder) {
    //...一系列是否需要二级回收的判断
    if (forceRecycle || holder.isRecyclable()) {
        if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                    //判断mCachedViews的大小是否大于2
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        //重点分析一
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }
                    //...计算targetCacheIndex的下标 让mCachedViews满足队列先进先出原则
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                if (!cached) {
                    //...如果二级缓存没有存储则添加到四级缓存
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
     } else {
         //...
     }
     //...
}
/**
* 重点分析一:7.RecyclerView.recycleViewHolderInternal
* 作用:如果mCachedViews的大小大于2则内部调用addViewHolderToRecycledViewPool方法添加到RecycledViewPool中 
*/        
void recycleCachedViewAt(int cachedViewIndex) {
    
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    //Viewholder存储到四级缓存
    addViewHolderToRecycledViewPool(viewHolder, true);
    //Viewholder在四级缓存存储后移除mCachedViews中对应的Viewholder
    mCachedViews.remove(cachedViewIndex);
}     

③分析四级缓存

/**
* 8.RecyclerView.recycleViewHolderInternal
*/
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
     clearNestedRecyclerViewIfNotNested(holder);
     //...
     if (dispatchRecycled) {
         dispatchViewRecycled(holder);
     }
     holder.mOwnerRecyclerView = null;
     //存储到RecycledViewPool中
     getRecycledViewPool().putRecycledView(holder);
}
/**
* 9.RecyclerView.recycleViewHolderInternal
*/
public void putRecycledView(ViewHolder scrap) {
    final int viewType = scrap.getItemViewType();
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
    //每个类型 viewType 最多只能缓存5个viewHolder 这也是为啥recycleView超过5列后会频繁调用createViewHolder()的原因
    if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
         return;
    }
    //...
    scrap.resetInternal();
    //将viewHolder添加到RecycledViewPool.ScrapData.scrapHeap中
    scrapHeap.add(scrap);
}

④ mCachedViews存储满了后(默认是2个)后,存储到RecycledViewPool中
在这里插入图片描述
⑤ 最后:三种缓存最终都是存储到ArrayList中,(Bugly博客偷一张图

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android从源码分析RecyclerView四级缓存复用机制一(缓存ViewHolder) 的相关文章

随机推荐

  • GD32F105的CAN通讯,可以发送数据,但接收不到数据

    项目简介 使用的芯片型号GD32F105VC 芯片资源CAN1 波特率500k 调试过程中发现发送数据正常 但是接收不到数据 总结几点注意事项如下 1 需要设置滤波器 若未设置滤波器 则接收不到数据 傻傻的认为滤波器配置问题 以为注释掉滤波
  • vue-vuetify-admin案例讲解

    vue vuetify admin案例讲解 1 Introduction 1 1 directory structure 1 2 vue cli 1 3 vuex 1 3 1 在store目录创建index js 1 3 2 在main j
  • 队列(一种遵循先进先出原则的数据结构)

    目录 1 队列 Queue 2 队列的抽象数据类型 队列ADT 3 队列接口 4 利用数组实现队列 4 1 队列的实现 4 2 利用数组实现队列的优势与缺点 5 利用单链表实现队列 5 1 队列的实现 5 2 利用单链表实现队列的优势与缺点
  • js对象的继承

    学无止境 望君把握时间 首先我们需要定义一个类 定义一个动物类 function Animal name 属性 this name name Animal 实例方法 this sleep function console log this
  • js增加class或者删除class

    1 比较传统的方法 var classVal document getElementById id getAttribute class 删除的话 classVal classVal replace someClassName docume
  • GAMES101: 现代计算机图形学入门(2)几何、光线追踪

    GAMES101 现代计算机图形学入门 链接 GAMES101 1 几何 1 1 几何的表示 隐式几何 通过一个函数表达式来表示的几何体 即 f x y z 0 优点 很容易判断一个点在不在几何体上 缺点 很难通过表达式看出几何体的形状 显
  • 菜鸟求职记6

    来到古城已经整整38天了 本想快快的找到工作然后做自己这三年来都没有做的事情 旅游 看电视 打篮球 打乒乓球 可是 事实却并非如此 这一个多月的苦衷可以说是一言难尽呀 到了此时此刻 恐怕每一个人都已经累得奄奄一息了 每个人曾经的自信都被现实
  • StrongSORT:Make DeepSORT Great Again

    1北京邮电大学2中国网络系统与网络文化北京市重点实验室 摘要 现有的多目标跟踪 Multi Object Tracking MOT 方法大致可以分为基于检测的跟踪和联合检测关联两种范式 虽然后者引起了更多的关注 并显示出与前者相当的性能 但
  • 在Android studio中Intent的几种基本使用方法

    在Android开发中 Intent是最基本也是最常用的操作 在Activity Service BroadcastReceiver这些核心组件中也需要Intent进行操作 下面我们具体介绍Intent在开发中的一些基本用法 假定目前有Fi
  • 第三方支付 -----支付宝支付流程

    大家都知道 第三方支付 已经普遍都在使用 所以我今天就说一下支付宝的支付流程 首先进入支付宝平台 点击开发中心 研发服务 获得沙盒的appid以及商户公钥和支付宝公钥 然后利用秘钥生成软件生成私钥和公钥 建立keys文件夹 将私钥和公钥文件
  • 将日期字符串转成LocalDateTime

    如果直接用LocalDateTime parse将日期字符串 yyyy MM dd 转成LocalDateTime会导致报错 所以我这里提供了将日期字符串转成LocalDateTime的方法 仅供参考 如有更好方式 欢迎大家分享 impor
  • WSL2报错:nvidia-smi Command ‘nvidia-smi‘ not found, but can be installed with:

    这里写自定义目录标题 找了很多方法 解决 分割线 WSL2部署 找了很多方法 在社区找了很多方法 结果在b站评论区找到了一个方法给解决了 原本一开始有人说是驱动版本问题 我nvcc V是ok的 但是nvidia smi一直报错 Comman
  • LaTeX排版(一):字体、页眉页脚、页边距、行距的设置

    目录 字体设置 布局设置 页眉页脚设置 行距的设置 其他 字体设置 字体设置需要用到宏包fontspec 需要在导言区添加如下指令 usepackage fontspec 中英文字体都可以分为如下3种 正文字体族 无衬线字体族 打字机字体族
  • 十进制转十六进制 C++

    目录 题目描述 思路分析 AC代码 题目描述 编写一个函数 传入一个十进制的正整数 将十进制整数转换为十六进制的字符串并返回 十六进制字符串中的字母全部大写 输入描述 键盘输入一个十进制的正整数 输出描述 输出该十进制整数转换后的十六进制字
  • 硬盘柱面损坏怎么办_最靠谱的机械硬盘坏道修复工具一:DiskGenius

    DiskGenius是一款硬盘分区 数据修复软件 DiskGenius的功能非常丰富 然而很多时候 我们都只是用DiskGenius来分区硬盘 对硬盘进行一些常规性能的操作 常常忽略了DiskGenius最重要的一个功能 那就是机械硬盘的坏
  • vue 按钮权限

    项目中按钮的操作权限我们可以直接使用 v if 判断就行 但是每个页面都要写一堆判断不太雅观 所以 可以写一个全局函数或者自定义指令 两种方式优雅的实现 一 全局函数 一般在登陆接口中后台就把权限列表信息提供了 可以把他存到缓存或者vuex
  • 从零开始完成YOLOv5目标识别(三)用PyQt5展示YOLOv5的识别结果

    往期内容 从零开始完成Yolov5目标识别 二 制作并训练自己的训练集 从零开始完成Yolov5目标识别 一 准备工作 目录 往期内容 一 项目框架 二 核心内容 1 QtDesign设计 2 检测部分 2 1 导包 2 2 main py
  • js-image-compressor 图片压缩插件

    1 安装插件 npm i js image compressor 2 引入 import ImageCompressor from js image compressor 3 使用 compressionImage file return
  • 加入ehcache后,系统出现内存泄漏,解决办法

    最近在系统中 加入缓存ehcache 但发现 每隔一天 服务器就会报出内存溢出 问题严重 后来在网上查资料发现 一篇解释的网文 spring中的提供了一个名为org springframework web util Introspector
  • Android从源码分析RecyclerView四级缓存复用机制一(缓存ViewHolder)

    RecyclerView相比较ListView先说多了多布局和缓存 目前已经在Android列表中大量普及使用 面试中也经常问到 所以对于RecyclerView的四级缓存机制也叫复用回收机制的分析很有必要 这部分很重要请 全村人来听 先说