当前位置: 首页 > news >正文

深入源码分析RecyclerView缓存复用原理

文章目录

    • 前言
      • 四级缓存
    • 源码分析
      • 缓存
        • 一级缓存(mChangedScrap和mChangedScrap)
        • 二级缓存(mCachedViews)
        • 三级缓存(ViewCacheExtension)
        • 四级缓存(mRecyclerPool)
          • 缓存池mRecyclerPool结构理解
          • 四级缓存简单小结
        • 缓存流程图
      • 复用
        • 复用流程图
    • 结语

前言

RecyclerView是Android日常开发中经常使用的控件,了解其源码,明白其中的缓存复用机制是十分有必要的;

四级缓存

我们都知道RecyclerView有四级缓存,缓存的都是ViewHolder对象,那都分别对应哪些缓存呢?各自缓存的作用是什么呢?这里先简单总结下:

层级缓存变量容量数据结构作用
1mChangedScrap与 mAttachedScrapXArrayList<ViewHolder>用来缓存还在屏幕内的ViewHolder
2mCachedViews默认为2,可通过调用setViewCacheSize()方法调整ArrayList<ViewHolder>用来缓存移除屏幕之外的ViewHolder
3mViewCacheExtensionX自定义缓存,一般不使用
4mRecyclerPool每个itemViewType默认存储5个ViewHolderSparseArray<ScrapData>ViewHolder缓存池,复用时需要重新调用onBindViewHolder

其中ScrapData结构如下:

      static class ScrapData {final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();}

源码分析

缓存

我们从RecyclerViewonLayout方法开始跟踪:

    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结构理解

缓存池mRecyclerPool结构理解

四级缓存简单小结

根据ViewHolder对应的itemViewType从缓存池中获取对应的ScrapData对象,ScrapData对象内部存储了ArrayList<ViewHolder> 集合,如果当前集合已满5个,则丢弃ViewHolder不进行缓存,如果集合不满,则先将ViewHolder进行数据漂白,清除相关信息后再添加到缓存集合中!

缓存流程图

缓存流程图

复用

复用流程图

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

相关文章:

深入源码分析RecyclerView缓存复用原理

文章目录 前言四级缓存 源码分析缓存一级缓存&#xff08;mChangedScrap和mChangedScrap&#xff09;二级缓存&#xff08;mCachedViews&#xff09;三级缓存&#xff08;ViewCacheExtension&#xff09;四级缓存&#xff08;mRecyclerPool&#xff09;缓存池mRecyclerPool结构…...

内网隧道代理技术(一)之内网隧道代理概述

内网隧道代理技术 内网转发 在渗透测试中&#xff0c;当我们获得了外网服务器&#xff08;如web服务器&#xff0c;ftp服务器&#xff0c;mali服务器等等&#xff09;的一定权限后发现这台服务器可以直接或者间接的访问内网。此时渗透测试进入后渗透阶段&#xff0c;一般情况…...

设计图形用户界面的原则

1) 一般性原则&#xff1a;界面要具有一致性、常用操作要有快捷方式、 提供简单的错误处理、对操作人员的重要操作要有信息反馈、操作可 逆、设计良好的联机帮助、合理划分并高效地使用显示屏、保证信息 显示方式与数据输入方式的协调一致 2) 颜色的使用&#xff1a;颜色…...

1:操作系统导论

1.1操作系统的定义 •Anoperatingsystemactsanintermediarybetweenuserofacomputerandthecomputer hardware. ◦ 操作系统充当计算机⽤⼾和计算机硬件之间的中介 •Thepurposeofanoperatingsystemistoprovideanenvironmentinwhichausercanexecute programsinaconvenientandeff…...

什么是微软的 Application Framework?

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天来看一下什么是微软的 Application Framework&#xff1f; 到底什么是 Application Framework&#xff1f; 还没有真正掌握任何一套Application Framework的使用之前&#xff0c;就来研究这个真的不是很…...

一个关于宏定义的问题,我和ChatGPT、NewBing、Google Bard、文心一言 居然全军覆没?

文章目录 一、问题重述二、AI 解题2.1 ChatGPT2.2 NewBing2.3 Google Bard2.4 文心一言2.5 小结 一、问题重述 今天在问答模块回答了一道问题&#xff0c;要睡觉的时候&#xff0c;又去看了一眼&#xff0c;发现回答错了。 问题描述&#xff1a;下面的z的值是多少。 #define…...

【服务器数据恢复】断电导致RAID无法找到存储设备的数据恢复案例

服务器数据恢复环境&#xff1a; HP EVA存储&#xff0c;6块SAS硬盘组建的raid5磁盘阵列。上层操作系统是WINDOWS SERVER。该存储为公司内部文件服务器使用。 服务器故障&分析&#xff1a; 在遭遇两次意外断电后&#xff0c;设备重启时raid提示“无法找到存储设备”。管理员…...

Windows上不可或缺的5款宝藏软件,工作效率拉满!

职场小白与大牛的区别&#xff1a;小白需要耗费大半天琢磨的事情&#xff0c;而大牛可以只花5分钟就能处理。 “牛人”&#xff0c;即拥有过人之处&#xff0c;专业、经验、技术等等&#xff0c;学会灵活运用高效率的工具也是关键的一点。工具找得好&#xff0c;运用得快&#…...

链表内指定区间反转

题目&#xff1a; 将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转&#xff0c;要求时间复杂度 O(n)&#xff0c;空间复杂度 O(1)。 例如&#xff1a; 给出的链表为 1→2→3→4→5→NULL&#xff0c;m2&#xff0c;n4 返回 1→4→3→2→5→NULL 数据范围&#xff…...

Vue中如何进行地图展示与交互(如百度地图、高德地图)?

Vue中如何进行地图展示与交互 随着移动互联网的普及&#xff0c;地图应用已经成为人们生活中不可或缺的一部分。在Vue.js中&#xff0c;我们可以使用第三方地图库&#xff08;如百度地图、高德地图&#xff09;来实现地图的展示和交互。本文将介绍如何在Vue.js中使用百度地图和…...

uni-app组件概述

1、组件 1.1、组件的含义 组件是视图层的基本组成单元。 组件是一个单独且可复用的功能模块的封装。 组件&#xff0c;包括&#xff1a;以组件名称为标记的开始标签和结束标签、组件内容、组件属性、组件属性值。 <component-name>是开始标签&#xff0c;</compon…...

什么是防火墙?它有什么作用?

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 作者会持续更新网络知识和python基础知识&#xff0c;期待你的关注 目录 一、什么是防火墙 二、防火墙的分类 1、软件防火墙 2、硬件防火墙 三、防火墙的作用 1、防止病毒 2、防止访问不安全内容 3、阻…...

基础工程(cubeide串口调试,printf实现,延时函数)

0.基础工程&#xff08;cubeide串口调试&#xff0c;printf实现&#xff0c;延时函数&#xff09; 文章目录 0.基础工程&#xff08;cubeide串口调试&#xff0c;printf实现&#xff0c;延时函数&#xff09;外部时钟源CLOCK(RCC)系统时钟SYS与DEBUG设置UART串口设置cubeide设置…...

大厂设计师都在用的9个灵感工具

每一件伟大的设计作品都离不开设计师灵感的爆发。设计师有很多灵感来源&#xff0c;比如精美的摄影图片、酷炫的网站设计、APP的特色功能、友好的用户体验动画&#xff0c;或者一篇文章。 设计师每天都需要收集灵感&#xff0c;把灵感收集当成日常生活。在这篇文章中&#xff…...

安全实现SpringBoot配置文件自动加解密

需求背景 应用程序开发的时候&#xff0c;往往会存在一些敏感的配置属性 数据库账号、密码第三方服务账号密码内置加密密码其他的敏感配置 对于安全性要求比较高的公司&#xff0c;往往不允许敏感配置以明文的方式出现。 通常做法是对这些敏感配置进行加密&#xff0c;然后在…...

数据结构--队列2--双端队列--java双端队列

介绍 双端队列&#xff0c;和前面学的队列和栈的区别在于双端队列2端都可以进行增删&#xff0c;其他2个都是只能一端可以增/删。 实现 链表 因为2端都需要可以操作所以我们使用双向链表 我们也需要一共头节点 所以节点设置 static class Node<E>{E value;Node<E…...

网络安全:信息收集专总结【社会工程学】

前言 俗话说“渗透的本质也就是信息收集”&#xff0c;信息收集的深度&#xff0c;直接关系到渗透测试的成败&#xff0c;打好信息收集这一基础可以让测试者选择合适和准确的渗透测试攻击方式&#xff0c;缩短渗透测试的时间。 一、思维导图 二、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…...

使用腾讯手游助手作为开发测试模拟器的方案---以及部分问题的解决方案

此文主要介绍使用第三方模拟器(这里使用腾讯手游助手)作为开发工具&#xff0c;此模拟器分为两个引擎&#xff0c;一个与其他模拟器一样基于virtualbox的标准引擎&#xff0c;不过优化不太好&#xff0c;一个是他们主推的aow引擎&#xff0c;此引擎。关于aow没有太多的技术资料…...

牛客网论坛最具争议的Linux内核成神笔记,GitHub已下载量已过百万

原文地址&#xff1a;牛客网论坛最具争议的Linux内核成神笔记&#xff0c;GitHub已下载量已过百万 1、前言 Linux内核是一个操作系统&#xff08;OS&#xff09;内核&#xff0c;本质上定义为类Unix。它用于不同的操作系统&#xff0c;主要是以不同的Linux发行版的形式。Linu…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

云原生安全实战:API网关Envoy的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关 作为微服务架构的统一入口&#xff0c;负责路由转发、安全控制、流量管理等核心功能。 2. Envoy 由Lyft开源的高性能云原生…...