深入源码分析RecyclerView缓存复用原理
文章目录
- 前言
- 四级缓存
- 源码分析
- 缓存
- 一级缓存(mChangedScrap和mChangedScrap)
- 二级缓存(mCachedViews)
- 三级缓存(ViewCacheExtension)
- 四级缓存(mRecyclerPool)
- 缓存池mRecyclerPool结构理解
- 四级缓存简单小结
- 缓存流程图
- 复用
- 复用流程图
- 结语
前言
RecyclerView
是Android日常开发中经常使用的控件,了解其源码,明白其中的缓存复用机制是十分有必要的;
四级缓存
我们都知道RecyclerView
有四级缓存,缓存的都是ViewHolder
对象,那都分别对应哪些缓存呢?各自缓存的作用是什么呢?这里先简单总结下:
层级 | 缓存变量 | 容量 | 数据结构 | 作用 |
---|---|---|---|---|
1 | mChangedScrap与 mAttachedScrap | X | ArrayList<ViewHolder> | 用来缓存还在屏幕内的ViewHolder |
2 | mCachedViews | 默认为2,可通过调用setViewCacheSize()方法调整 | ArrayList<ViewHolder> | 用来缓存移除屏幕之外的ViewHolder |
3 | mViewCacheExtension | X | 自定义缓存,一般不使用 | |
4 | mRecyclerPool | 每个itemViewType默认存储5个ViewHolder | SparseArray<ScrapData> | ViewHolder缓存池,复用时需要重新调用onBindViewHolder |
其中ScrapData
结构如下:
static class ScrapData {final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();}
源码分析
缓存
我们从RecyclerView
的onLayout
方法开始跟踪:
protected void onLayout(boolean changed, int l, int t, int r, int b) {...dispatchLayout();...}
其中dispatchLayout()
方法如下:
void dispatchLayout() {if (mAdapter == null) {Log.e(TAG, "No adapter attached; skipping layout");return;}if (mLayout == null) {Log.e(TAG, "No layout manager attached; skipping layout");return;}mState.mIsMeasuring = false;if (mState.mLayoutStep == State.STEP_START) {//dispatchLayoutStep1()中会做以下几件事:1.处理适配器的更新;2.决定应该运行哪个动画;3.保存有关当前视图的信息;4.运行预测布局并保存其信息;dispatchLayoutStep1();mLayout.setExactMeasureSpecsFrom(this);//dispatchLayoutStep2()中会进行实际的布局操作dispatchLayoutStep2();} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()|| mLayout.getHeight() != getHeight()) {// 当宽高改变时,会再次调用 dispatchLayoutStep2()进行重新布局;mLayout.setExactMeasureSpecsFrom(this);dispatchLayoutStep2();} else {// always make sure we sync them (to ensure mode is exact)mLayout.setExactMeasureSpecsFrom(this);}//dispatchLayoutStep3()处理相关动画dispatchLayoutStep3();}
这里我们重点关注下 dispatchLayoutStep2()
方法;
private void dispatchLayoutStep2() {...mLayout.onLayoutChildren(mRecycler, mState);...}
显然,由于dispatchLayoutStep2()
主要工作是重新布局,那么肯定要进行子View的布局;
其中 mLayout.onLayoutChildren(mRecycler, mState);
调用的是LayoutManager的onLayoutChildren
方法,
这里,我们选择LinearLayoutManager
来跟进流程;
### LinearLayoutManager.onLayoutChildrenpublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {...detachAndScrapAttachedViews(recycler);...}
onLayoutChildren会调用detachAndScrapAttachedViews(recycler)方法
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {final int childCount = getChildCount();for (int i = childCount - 1; i >= 0; i--) { final View v = getChildAt(i);scrapOrRecycleView(recycler, i, v);}}
注意这里是倒序遍历,我们重点看看scrapOrRecycleView(recycler, i, v);
方法;
final ViewHolder viewHolder = getChildViewHolderInt(view);//如果viewHolder设置成ignore,则直接返回;if (viewHolder.shouldIgnore()) { if (DEBUG) {Log.d(TAG, "ignoring view " + viewHolder);}return;}//如果viewHolder数据非法无效 && viewHolder不指向数据集中移除的数据 && adapter没有设置stableId if (viewHolder.isInvalid() && !viewHolder.isRemoved()&& !mRecyclerView.mAdapter.hasStableIds()) {//移除当前子ViewremoveViewAt(index);//里面会调用mCachedViews和mRecyclerPool进行二级和四级缓存(三级缓存为自定义缓存)recycler.recycleViewHolderInternal(viewHolder);} else {//暂时将View解绑,以便后续可以通过ViewHolder重新绑定复用detachViewAt(index);//里面会根据条件调用mAttachedScrap或mChangedScrap进行一级缓存;recycler.scrapView(view);//从消失列表中移除viewHoldermRecyclerView.mViewInfoStore.onViewDetached(viewHolder);}
接下来,我们就重点分别看recycler.scrapView(view)
和 recycler.recycleViewHolderInternal(viewHolder)
方法;
一级缓存(mChangedScrap和mChangedScrap)
void scrapView(View view) {final ViewHolder holder = getChildViewHolderInt(view);//如果ViewHolder标记为移除或失效的 || ViewHolder没有变化 || item 无动画或动画不复用if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {throw new IllegalArgumentException("Called scrap view with an invalid view."));}holder.setScrapContainer(this, false);mAttachedScrap.add(holder);} else {if (mChangedScrap == null) {mChangedScrap = new ArrayList<ViewHolder>();}holder.setScrapContainer(this, true);mChangedScrap.add(holder);}}
从上述代码可以看出:当ViewHolder满足移除或失效||没有变化||没有动画或动画不复用时
,缓存到mAttachedScrap
集合中,否则缓存到mChangedScrap
集合中;
二级缓存(mCachedViews)
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)) {// 先获取mCachedViews的大小int cachedViewSize = mCachedViews.size();//如果mCachedViews大小超过或等于默认值2的时候if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {recycleCachedViewAt(0);//将下标为0位置的元素从集合中移除,放入到四级缓存mRecyclerPool中cachedViewSize--; //集合大小-1}int targetCacheIndex = cachedViewSize; //将cachedViewSize赋值给targetCacheIndexif (ALLOW_THREAD_GAP_WORK&& cachedViewSize > 0&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {...//缓存新的holder至targetCacheIndex下标中,并设置cached为truemCachedViews.add(targetCacheIndex, holder);cached = true;}if (!cached) {//没有缓存成功,则放入到四级缓存mRecyclerPool中addViewHolderToRecycledViewPool(holder, true);recycled = true;}} ...}
从上述代码中可以看出:当满足移除屏幕条件时:
1. 当mCachedViews没满时,ViewHolder会直接缓存到mCachedViews中,如果缓存失败,则会缓存到四级缓存mRecyclerPool中;
2. 当mCachedViews满时,会先移除mCachedViews集合中下标为0位置的元素,并将其放置到缓存池mRecyclerPool中;然后将ViewHolder缓存到mCachedViews集合下标为1位置上,如果缓存失败,则会缓存到四级缓存mRecyclerPool中;
三级缓存(ViewCacheExtension)
为用户自定义缓存,可通过自定义ViewCacheExtension
,并重写getViewForPositionAndType
方法实现;
四级缓存(mRecyclerPool)
从上面二级缓存实现可以看到,会调用addViewHolderToRecycledViewPool(holder, true)
实现四级缓存机制;
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {//1.将viewHolder引用的recyclerView移除掉clearNestedRecyclerViewIfNotNested(holder);...//2.移除viewHolder相关监听if (dispatchRecycled) {dispatchViewRecycled(holder);}holder.mOwnerRecyclerView = null;//3.缓存至mRecyclerPool中;getRecycledViewPool().putRecycledView(holder);}
### getRecycledViewPool().putRecycledViewpublic void putRecycledView(ViewHolder scrap) {//1.先获取ViewHolder对象的itemViewTypefinal int viewType = scrap.getItemViewType();//2.根据itemViewType获取对应的ArrayList<ViewHolder>集合final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;//3.如果集合中已经保存有5个ViewHolder了,那就不再进行缓存操作;if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {return;}//4.已经缓存的有,抛异常if (DEBUG && scrapHeap.contains(scrap)) {throw new IllegalArgumentException("this scrap item already exists");}//5.将ViewHolder进行`漂白`,清除相关标志、位置信息等等,因此复用缓存池中的ViewHolder需要重新进行绑定操作;scrap.resetInternal();//6.添加到缓冲池中;scrapHeap.add(scrap);}
缓存池mRecyclerPool结构理解
四级缓存简单小结
根据ViewHolder对应的itemViewType从缓存池中获取对应的ScrapData对象,ScrapData对象内部存储了ArrayList<ViewHolder> 集合,如果当前集合已满5个,则丢弃ViewHolder不进行缓存,如果集合不满,则先将ViewHolder进行数据漂白,清除相关信息后再添加到缓存集合中!
缓存流程图
复用
复用流程图
结语
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )
相关文章:
深入源码分析RecyclerView缓存复用原理
文章目录 前言四级缓存 源码分析缓存一级缓存(mChangedScrap和mChangedScrap)二级缓存(mCachedViews)三级缓存(ViewCacheExtension)四级缓存(mRecyclerPool)缓存池mRecyclerPool结构…...
内网隧道代理技术(一)之内网隧道代理概述
内网隧道代理技术 内网转发 在渗透测试中,当我们获得了外网服务器(如web服务器,ftp服务器,mali服务器等等)的一定权限后发现这台服务器可以直接或者间接的访问内网。此时渗透测试进入后渗透阶段,一般情况…...
设计图形用户界面的原则
1) 一般性原则:界面要具有一致性、常用操作要有快捷方式、 提供简单的错误处理、对操作人员的重要操作要有信息反馈、操作可 逆、设计良好的联机帮助、合理划分并高效地使用显示屏、保证信息 显示方式与数据输入方式的协调一致 2) 颜色的使用:颜色…...
1:操作系统导论
1.1操作系统的定义 •Anoperatingsystemactsanintermediarybetweenuserofacomputerandthecomputer hardware. ◦ 操作系统充当计算机⽤⼾和计算机硬件之间的中介 •Thepurposeofanoperatingsystemistoprovideanenvironmentinwhichausercanexecute programsinaconvenientandeff…...
什么是微软的 Application Framework?
我是荔园微风,作为一名在IT界整整25年的老兵,今天来看一下什么是微软的 Application Framework? 到底什么是 Application Framework? 还没有真正掌握任何一套Application Framework的使用之前,就来研究这个真的不是很…...
一个关于宏定义的问题,我和ChatGPT、NewBing、Google Bard、文心一言 居然全军覆没?
文章目录 一、问题重述二、AI 解题2.1 ChatGPT2.2 NewBing2.3 Google Bard2.4 文心一言2.5 小结 一、问题重述 今天在问答模块回答了一道问题,要睡觉的时候,又去看了一眼,发现回答错了。 问题描述:下面的z的值是多少。 #define…...
【服务器数据恢复】断电导致RAID无法找到存储设备的数据恢复案例
服务器数据恢复环境: HP EVA存储,6块SAS硬盘组建的raid5磁盘阵列。上层操作系统是WINDOWS SERVER。该存储为公司内部文件服务器使用。 服务器故障&分析: 在遭遇两次意外断电后,设备重启时raid提示“无法找到存储设备”。管理员…...
Windows上不可或缺的5款宝藏软件,工作效率拉满!
职场小白与大牛的区别:小白需要耗费大半天琢磨的事情,而大牛可以只花5分钟就能处理。 “牛人”,即拥有过人之处,专业、经验、技术等等,学会灵活运用高效率的工具也是关键的一点。工具找得好,运用得快&#…...
链表内指定区间反转
题目: 将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。 例如: 给出的链表为 1→2→3→4→5→NULL,m2,n4 返回 1→4→3→2→5→NULL 数据范围ÿ…...
Vue中如何进行地图展示与交互(如百度地图、高德地图)?
Vue中如何进行地图展示与交互 随着移动互联网的普及,地图应用已经成为人们生活中不可或缺的一部分。在Vue.js中,我们可以使用第三方地图库(如百度地图、高德地图)来实现地图的展示和交互。本文将介绍如何在Vue.js中使用百度地图和…...
uni-app组件概述
1、组件 1.1、组件的含义 组件是视图层的基本组成单元。 组件是一个单独且可复用的功能模块的封装。 组件,包括:以组件名称为标记的开始标签和结束标签、组件内容、组件属性、组件属性值。 <component-name>是开始标签,</compon…...
什么是防火墙?它有什么作用?
作者:Insist-- 个人主页:insist--个人主页 作者会持续更新网络知识和python基础知识,期待你的关注 目录 一、什么是防火墙 二、防火墙的分类 1、软件防火墙 2、硬件防火墙 三、防火墙的作用 1、防止病毒 2、防止访问不安全内容 3、阻…...
基础工程(cubeide串口调试,printf实现,延时函数)
0.基础工程(cubeide串口调试,printf实现,延时函数) 文章目录 0.基础工程(cubeide串口调试,printf实现,延时函数)外部时钟源CLOCK(RCC)系统时钟SYS与DEBUG设置UART串口设置cubeide设置…...
大厂设计师都在用的9个灵感工具
每一件伟大的设计作品都离不开设计师灵感的爆发。设计师有很多灵感来源,比如精美的摄影图片、酷炫的网站设计、APP的特色功能、友好的用户体验动画,或者一篇文章。 设计师每天都需要收集灵感,把灵感收集当成日常生活。在这篇文章中ÿ…...
安全实现SpringBoot配置文件自动加解密
需求背景 应用程序开发的时候,往往会存在一些敏感的配置属性 数据库账号、密码第三方服务账号密码内置加密密码其他的敏感配置 对于安全性要求比较高的公司,往往不允许敏感配置以明文的方式出现。 通常做法是对这些敏感配置进行加密,然后在…...
数据结构--队列2--双端队列--java双端队列
介绍 双端队列,和前面学的队列和栈的区别在于双端队列2端都可以进行增删,其他2个都是只能一端可以增/删。 实现 链表 因为2端都需要可以操作所以我们使用双向链表 我们也需要一共头节点 所以节点设置 static class Node<E>{E value;Node<E…...
网络安全:信息收集专总结【社会工程学】
前言 俗话说“渗透的本质也就是信息收集”,信息收集的深度,直接关系到渗透测试的成败,打好信息收集这一基础可以让测试者选择合适和准确的渗透测试攻击方式,缩短渗透测试的时间。 一、思维导图 二、GoogleHacking 1、介绍 利用…...
Linux 命令总结
基本操作 Linux关机,重启 # 关机 shutdown -h now# 重启 shutdown -r now 查看系统,CPU信息 # 查看系统内核信息 uname -a# 查看系统内核版本 cat /proc/version# 查看当前用户环境变量 envcat /proc/cpuinfo# 查看有几个逻辑cpu, 包括cpu型号 cat /proc/cpuinfo | grep na…...
使用腾讯手游助手作为开发测试模拟器的方案---以及部分问题的解决方案
此文主要介绍使用第三方模拟器(这里使用腾讯手游助手)作为开发工具,此模拟器分为两个引擎,一个与其他模拟器一样基于virtualbox的标准引擎,不过优化不太好,一个是他们主推的aow引擎,此引擎。关于aow没有太多的技术资料…...
牛客网论坛最具争议的Linux内核成神笔记,GitHub已下载量已过百万
原文地址:牛客网论坛最具争议的Linux内核成神笔记,GitHub已下载量已过百万 1、前言 Linux内核是一个操作系统(OS)内核,本质上定义为类Unix。它用于不同的操作系统,主要是以不同的Linux发行版的形式。Linu…...
docker如何容器迁移(实战)
手把手教你如何做容器迁移 第一步准备数据 假设要迁移一个 mysql 服务(docker部署),由于数据库过大(超过50 GB),用mysqldump备份和还原则太过耗时,下面尝试拷贝目录的方式来迁移,详…...
Android kotlin序列化之Parcelable详解与使用(二)
一、介绍 注解序列化篇:Android kotlin序列化之Parcelize详解与使用_蜗牛、Z的博客-CSDN博客 通过上一篇注解序列化,我们已了解的kotlin的序列化比Java复杂了很多。而且有好多问题,注解虽好,但是存在一些问题。 一般在大型商业…...
C++ 类设计的实践与理解
前言 C代码提供了足够的灵活性,因此对于大部分工程师来说都很难把握。本文介绍了写好C代码需要遵循的最佳实践方法,并在最后提供了一个工具可以帮助我们分析C代码的健壮度。 1. 尽可能尝试使用新的C标准 到2023年,C已经走过了40多个年头。新…...
循环链表的创建
循环链表的介绍及创建(C语言代码实现) 点击打开在线编译器,边学边练 循环链表概念 对于单链表以及双向链表,其就像一个小巷,无论怎么样最终都能从一端走到另一端,然而循环链表则像一个有传送门的小巷&…...
如何让GPT的回答令人眼前一亮,不再刻板回复!
我们平常在使用GPT的时候,是否觉得它的回复太过于死板、官方化,特别是用于创作、写论文分析的时候,内容往往让读者提不起兴趣、没有吸引人的地方,甚至有些内容百度都可以搜到。 举个例子,如下图: 问GPT,AI…...
JMeter测试笔记(四):逻辑控制器
引言: 进行性能测试时,我们需要根据不同的情况来设置不同的执行流程,而逻辑控制器可以帮助我们实现这个目的。 在本文中,我们将深入了解JMeter中的逻辑控制器,包括简单控制器、循环控制器等,并学习如何正…...
【计算机组成原理·笔记】I/O接口
I/O接口 概述I/O接口的功能和组成 I/O接口的组成I/O接口的功能 I/O接口类型 按数据传送方式按功能灵活性按通用性按数据传输的控制方式 概述 I/O接口通常是指主机与I/O设备之间设置的硬件电路以及相应的软件控制,主机通过I/O接口和I/O设备相连接。 I/O接口的功…...
MIT6.024学习笔记(二)——图论(1)
学习不是为了竞争和战胜他人,而是为了更好地了解自己和世界。 - 达赖喇嘛 文章目录 图的相关概念涂色问题基础涂色方法(贪婪算法)证明 二分图匹配问题应用:稳定婚烟问题算法性质及其证明 图的相关概念 图的定义:一组&…...
饼状图使用属性时,使用驼峰命名法
饼状图是使用D3.js等JavaScript库来绘制的,而JavaScript中的属性名通常采用驼峰式命名法,即第一个单词的首字母小写,后面单词的首字母大写,例如fontSize、fontWeight等。而CSS中的属性名采用连字符命名法,即单词之间用…...
使用Spring Boot、Spring Security和Thymeleaf的整合示例
使用Spring Boot、Spring Security和Thymeleaf的整合示例 大纲: 创建Spring Boot项目 集成Thymeleaf作为模板引擎 配置Spring Security实现身份验证和授权 创建登录页面和主页 创建管理员页面和普通用户页面 实现用户角色和权限管理 详细步骤: 创建Sprin…...
广州白云区哪里封了/一键关键词优化
ifstat介绍ifstat工具是个网络接口监测工具,比较简单看网络流量实例默认使用#ifstateth0 eth1KB/s in KB/s out KB/s in KB/s out0.07 0.20 0.00 0.000.07 0.15 0.58 0.00默认ifstat不监控回环接口,显示的流量单位是KB。监控所有网络接口# ifstat -alo eth0 eth1KB/…...
文化馆网站建设/百度咨询电话人工台
为了项目展示需要,要从Windows远程桌面连接Mac OS X,调研半天终于成功,记录如下:1.Mac OS X VNC服务设置Mac OS X 10.5已经增加支持了由VNC Viewer访问的功能,设置如下系统偏好设置-共享-勾选“屏幕共享”,…...
地方网站怎么做的/百度一下官网网址
1. 涉及平台 平台管理、商家端(PC端、手机端)、买家平台(H5/公众号、小程序、APP端(IOS/Android)、微服务平台(业务服务) 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…...
wordpress 前台加载慢/百度一下知道首页
1 Service Kubernetes Pods 是有生命周期的。他们会被创建,被销毁,但是他们不会无条件"复活"。比如 ReplicaSets 这种 Pod 控制器会动态生成或者销毁 Pods(如在扩容或者缩容时)。当然每个Pod 都会自动被 k8s 分配IP地址…...
做网站怎么单独写手机页面/宣传软文模板
Linux 文件或目录的属性主要包括:文件或目录的节点、种类、权限模式、链接数量、所归属的用户和用户组、最近访问或修改的时间等内容。具体情况如下: 命令: ls -lih 输出: [rootlocalhost test]# ls -lih total 0 51621141 drwxr-…...
俄罗斯做电商网站/谷歌关键词搜索量数据查询
1.#列表可重复,类型不同,用[]表示 listA [a, b, c, 1, 2] # 遍历list for item in listA:print(item)#元组是只读的,不能修改。元组用“()”表示 tuple1 (1,2,a,4,5,6) for item in tuple1:print(item)#字典定义了键…...