RecyclerView(三)—— RecyclerView的缓存机制

article/2025/11/10 16:51:44

RecyclerView内存优越性,得益于它独特的缓存机制。

1 如何复用表项

如果列表中的每个表项在移出屏幕时被销毁,移入时又被重新创建,是很消耗资源,所以RecyclerView引入了缓存机制。缓存是为了复用,复用的好处是有可能免去两个耗费资源的操作:创建表项视图和为每个表项视图绑定数据。

2 ViewHolder

2.1 作用

ViewHolder是对RecyclerView上的itemView的封装,它是RecyclerView缓存的载体。它封装了以下属性:

  • View itemView:对应RecyclerView的子View
  • int mPositionView当前对应数据在数据源中的位置;
  • int mOldPositionView上次绑定的数据在数据源中的位置;
  • long mItemId:可以判断ViewHolder是否需要重新绑定数据;
  • int mItemViewTypeitemView对应的类型;
  • int mPreLayoutPosition:在预布局阶段ViewHolder对应数据在数据源中的位置;
  • int mFlagsViewHolder对应的标记位;
  • List<Ojbect> mPayloads:实现局部刷新;
  • Recycler mScrapContainer:如果不为空,表示ViewHolder是存放在scrap缓存中;

2.2 flag

  • FLAG_BOUNDViewHolder对应的View已经绑定好了数据,无需重新绑定
  • FLAG_UPDATE:数据发生了变化,View需要重新绑定
  • FLAG_INVALID:数据失效了,View需要重新绑定
  • FLAG_REMOVED:数据从数据源中删除,View在消失动画中仍然有用
  • FLAG_NOT_RECYCLABLEViewHolder不能被回收,ViewHolder对应ItemView做动画时需要保证ViewHolder不能被回收掉
  • FLAG_RETURNED_FROM_SCRAP:从scrap缓存中获取到的ViewHolder
  • FLAG_IGNORE:如果回收该类型的ViewHolder会报错
  • FLAG_TMP_DETACHED:表示ItemViewRecyclerView上DETACHED了,detachremove的区别是,remove会将ViewViewGroupchildren数组中删除并且刷新ViewGroupdetach只会删除不会触发刷新
  • FLAG_ADAPTER_FULLUPDATE:表示ViewHolder需要全量更新,如果没有设置该标志位,则是局部更新
  • FLAG_MOVED:当ViewHolder的位置发生变化,做动画时需要使用
  • FLAG_APPEARED_IN_PRE_LAYOUTViewHolder出现在预布局中,需要做APPEARED动画

3 RecyclerView局部刷新

RecyclerView的数据更新,主要有以下几个方法:

  • notifyDataSetChanged():刷新全部可见的ViewHolder
  • notifyItemChanged(int position):刷新指定位置的ViewHolder;
  • notifyItemChanged(int position, @Nullable Object payload):刷新指定位置的ViewHolder,其中playload参数可以认为是要刷新的一个标示;
  • notifyItemRangeChanged(int positionStart, int itemCount):从指定的位置开始刷新指定个数的ViewHolder
  • notifyItemRangeChanged(int positionStart, int itemCount, Object payload):从指定的位置开始刷新指定个数的ViewHolder,其中playload参数可以认为是要刷新的一个标示;
  • notifyItemInserted(int position)notifyItemMoved(int fromPosition, int toPosition)notifyItemRangeInserted(int positionStart, int itemCount)notifyItemRemoved(int position)notifyItemRangeRemoved(int positionStart, int itemCount):插入、移动、移除并自动刷新;
@Override
public void onBindViewHolder(ViewHolderholder, int position, List<Object> payloads) {if (payloads.isEmpty()) {// payloads为空,说明是更新整个ViewHolderonBindViewHolder(holder, position);} else {// payloads不为空,这只更新需要更新的View即可。String payload = payloads.get(0).toString();if ("changeColor".equals(payload)) {holder.textView.setTextColor("");}}
}

4 四级缓存

以下是RecylcerView缓存机制的时序图:

RecyclerView的缓存机制

其中,Recycler用于表项的复用,RecyclerView通过Recycler获得下一个待绘制的表项。RecyclerView缓存基本上是通过三个内部类管理的,RecyclerRecycledViewPoolViewCacheExtension,以下是RecyclerView.Recycler的部分源码:

public class RecyclerView extends ViewGroup implements ScrollingView {public final class Recycler {final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();ArrayList<ViewHolder> mChangedScrap = null;final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();private final List<ViewHolder>mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;int mViewCacheMax = DEFAULT_CACHE_SIZE;RecycledViewPool mRecyclerPool;private ViewCacheExtension mViewCacheExtension;static final int DEFAULT_CACHE_SIZE = 2;}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<>();public void putRecycledView(ViewHolder scrap) {final int viewType = scrap.getItemViewType();final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {return;}if (DEBUG && scrapHeap.contains(scrap)) {throw new IllegalArgumentException("this scrap item already exists");}scrap.resetInternal();scrapHeap.add(scrap);}}public abstract static class ViewCacheExtension {@Nullablepublic abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position, int type);}
}

scrap [skræp] 碎片,小块; attach [əˈtætʃ] 系上,贴上 detached [dɪˈtætʃt] 单独的,分离的 extension [ɪkˈstenʃn] 延伸,扩展

由上可知,Recycler4个变量用来缓存ViewHolder对象,优先级由高到低依次为ArrayList<ViewHolder> mAttachedScrapArrayList<ViewHolder> mCachedViewsViewCacheExtension mViewCacheExtensionRecycledViewPool mRecyclerPoolRecycledViewPool ViewHolderviewType分类存储(通过SparseArray),同类ViewHolder存储在默认大小为5ArrayList中。

以下是它们的含义:

Recycler的成员变量

RecyclerView的四级缓存:

  • mChangedScrapmAttachedScrap是用来做屏幕内ViewHolder复用,不需要重新onCreateViewHolderonBindViewHolder
  • mCachedView是用来缓存最近移出屏幕的ViewHolder,包括数据和position信息。复用时必须是是相同位置的ViewHolder,应用场景是在那些来回滑动的列表中,往回滑动时,能直接复用ViewHolder的数据,不用onBindeViewHolder
  • mViewCacheExtension,自定义缓存,这个的创建和缓存完全由开发者自己控制,系统未在这里添加数据;
  • RecycledViewPoolViewHolder缓存池,当mCacheView满后或者adapter被更换,将mCacheView中移出的ViewHolder放到缓存池中,同时把ViewHolder的数据清除掉,所以复用的时候需要onBindeViewHolder

四级缓存

4.1 屏幕内缓存/一级缓存/scrap缓存(mAttachedScrapmChangedScrap

屏幕内缓存指在屏幕中显示的ViewHolder,这些ViewHolder会缓存在mAttachedScrapmChangedScrap中:

  • mAttachedScrap中存放的是没有与RecyclerView分离的ViewHolder列表,从其中复用的ViewHolder既不需要重新创建也不需要重新绑定数据;
  • mChangedScrap表示数据已经改变的ViewHolder列表,只有满足以下的条件ViewHolder才会被添加到mChangedScrap中:当它关联的itemView发生了变化(notifyItemChanged或者notifyItemRangeChanged被调用),并且ItemAnimator调用ViewHolder#canReuseUpdatedViewHolder方法时,返回了false(返回false表示要执行用一个view替换另一个 view的动画,例如淡入淡出动画。true表示动画在view内部发生)。否则,ViewHolder会被添加到mAttachedScrap中;

mChangedScrapmAttachedScrap可以看做是一个层级,都是屏幕上可见ViewHolder,只不过区分了状态(改变和未改变)。

4.2 屏幕外缓存/二级缓存(mCachedViews

当列表滑动出了屏幕时,ViewHolder会被缓存在mCachedViews ,其大小由mViewCacheMax决定,默认DEFAULT_CACHE_SIZE2,可通过Recyclerview.setItemViewCacheSize()动态设置。

mCachedViews中复用的ViewHolder ,只能复用于指定位置的表项。也可以说,mCachedViews是离屏缓存,用于缓存指定位置的ViewHolder ,只有“列表回滚”这一种场景(刚滚出屏幕的表项再次进入屏幕),才有可能命中该缓存。该缓存存放在默认大小为2ArrayList中;

4.3 自定义缓存/三级缓存(ViewCacheExtension

可以自己实现ViewCacheExtension类实现自定义缓存,可通过Recyclerview.setViewCacheExtension()设置。

4.4 缓存池/四级缓存(RecycledViewPool

ViewHolder在首先会缓存在mCachedViews中,当超过了个数(比如默认为2),就会添加到RecycledViewPool中。从mRecyclerPool中复用的ViewHolder,只能复用于viewType相同的表项,并且需要重新绑定数据。

RecycledViewPool会根据每个ViewTypeViewHolder分别存储在不同的列表中,每个ViewType最多缓存DEFAULT_MAX_SCRAP = 5 x ViewHolder,如果RecycledViewPool没有被多个RecycledView共享,对于线性布局,每个ViewType最多只有一个缓存,如果是网格有多少行就缓存多少个。它们之间的关系如下 :

缓存池

5 缓存策略

缓存流程:

  • 在插入或删除ViewHolder的时候,首先会把屏幕内的ViewHolder保存至mAttachedScrap中;
  • 滑动屏幕时,首先消失的ViewHolder会保存到mCachedViews中,mCachedView的大小默认是2,超过的话,就按照先入先出的原则,将移出头部的ViewHolder保存到RecyclerPool缓存池(如果有自定义的缓存就保存到自定义缓存中)。RecyclerPool缓存池会按照ViewHolderviewType进行保存,每个viewType缓存个数为5个,超过了就会被回收;

获取缓存流程:首先从mAttachedScrap中获取,通过position匹配ViewHolder,如果匹配失败,就会从mCachedViews中获取,也是通过position获取ViewHolder缓存;如果获取失败,就会继续从自定义缓存中获取;如果获取失败,会继续从mRecyclerPool中获取;如果获取失败,会重新创建ViewHolder 流程如下 :

缓存策略

Recyclerview在获取ViewHolder时按四级缓存的顺序查找,如果没找到就创建(createViewHolder)。其中只有RecycledViewPool找到时才会调用 onBindViewHolder,其它缓存不会重新onBindViewHolder

问:在RecyclerView中,滑动10ViewHolder,再滑动回去,会有几个ViewHolder执行onBindViewHolder?

假设页面可以容纳7条数据

  • 首先,页面内的7条数据会依次调用onCreateViewHolderonBindViewHolder
  • 之后,向下滑动1条(position = 7),会把position = 0的数据存放到mCachedViews中,此时mCachedViews缓存数量为1RecyclerPool中缓存数量为0position = 7的数据通过positionmCachedViews找不到对应的ViewHolder,通过viewTypemRecyclerPool中找不到对应的ViewHolder,所以会调用onCreateViewHolderonBindViewHolder方法;
  • 再往下滑动1条(position = 8),同position = 7position = 1的数据会存放到mCachedViews
  • 再往下滑动1条(position = 9),由于mCahcedViews的缓存区默认容量为2,所以,position = 0的数据会被清空,存放到mRecyclerPool缓存池中,而position = 2的数据会存放到mCachedViews,而position = 9的数据无法通过positionmCacheViews中获取,也无法通过viewTypemRecyclerPool中获取,所以还会调用onCreateViewHolderonBindViewHolder。此时,mCachedViews缓存区数量是2mRecyclerPool的数量为1;
  • 再往下滑动1条(position = 10),这个时候就可以通过viewTypemRecyclerPool中查找到ViewHolder,所以可以直接复用了,并通过onBindeViewHolder来绑定数据;
  • 依次类推

RecyclerView中,并不是每次绘制表项都会重新创建ViewHolder对象,也不是每次都会重新绑定ViewHolder数据。

  • 最坏的情况:重新创建ViewHolder并绑定数据;
  • 次好的情况:复用ViewHolder但重新绑定数据;
  • 最好的情况:复用ViewHolder且不重新绑定数据;

通过了解RecyclerView的四级缓存,可以知道,RecyclerView最多可以缓存N(屏幕最多可显示的item数) + 2(屏幕外的缓存) + 5 x M (M代表M个ViewType,缓存池的缓存),只有RecycledViewPool找到时才会重新调用bindViewHolder
还需要注意的是,RecycledViewPool可以被多个RecyclerView共享,其缓存个数与ViewType个数、布局相关,如果RecycledViewPool没有被多个RecycledView共享,对于线性布局,每个ViewType最多只有一个缓存,如果是网格布局有多少行就缓存多少个。

参考

https://juejin.cn/post/6844903778303344647
https://zhooker.github.io/2017/08/14/关于Recyclerview的缓存机制的理解/
http://www.lxiaoyu.com/p/286763
https://www.jianshu.com/p/b11c62871169
https://jishuin.proginn.com/p/763bfbd55a86
https://www.jianshu.com/p/443d741c7e3e


http://chatgpt.dhexx.cn/article/8ydy5foQ.shtml

相关文章

RecyclerView详解一,使用及缓存机制

本文大致会先讲解RecyclerView的基础知识及使用&#xff0c;最后会深入讲解一点原理。当然&#xff0c;本人知识水平有限哈&#xff0c;太深入的东西我现在还没接触到&#xff0c;还请大家包容&#xff0c;阿里嘎多~ 一、RecyclerView的历史与发展 既然讲到了RV&#xff0c;那…

Android开发—RecyclerView使用

1.RecyclerView是什么 RecyclerView 在Android中用于创建列表。 官网的解释为&#xff1a; RecyclerView 可以让您轻松高效地显示大量数据。您提供数据并定义每个列表项的外观&#xff0c;而 RecyclerView 库会根据需要动态创建元素。 当RecyclerView的列表项滚出屏幕的时候&a…

Android RecyclerView使用简述

RecyclerView使用简述 前言正文一、创建项目二、RecyclerView基本使用① item布局和适配器② 显示数据③ 添加Item点击事件④ 添加Item子控件点击事件⑤ 添加长按事件⑥ 多个子控件点击事件 三、RecyclerView ViewBinding使用① 适配器② 显示数据③ 添加控件点击和长按 四、R…

es6新特性总结及使用说明

目录 简介 新特性说明 let语法 const语法 解构赋值 模板字符串 对象简写 对象操作--深拷贝 箭头函数 小结 简介 1. ECMAScript 6.0是 JavaScript 语言的下一代标准&#xff0c; 2015 年 6 月发布。 ES6 设计目标是达到 JavaScript 语言可以用来编写复杂的大型程序&a…

ES6有哪些新特性

ES6有哪些新特性(1)变量声明:由var变为let和const; (2)模板字符串:使用反引号;在模板字符串里面支持换行,并可以在里面使用${}来包裹一个变量或表达式; (3)解构:有数组解构和对象解构;可以快速获取数组和对象的值; (4) 展开运算符:在ES6中用…来表示展开运算符,它可以将数组…

ES6 新特性知识点总结

文章目录 ES6let及const解构赋值模板字符串Symbol类型Set和Map数据结构箭头函数类 ES6 ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。 ES6 实际上是一个泛指&#xff0c;泛指 ES2015 及后续的版本。 每一次标准的诞生都意味着语言的…

ES6 新特性

1 、ES6 新特性 现在使用主流的前端框架中&#xff0c;如ReactJS、Vue.js、angularjs等&#xff0c;都会使用到ES6的新特性&#xff0c;作为一名高级工程师而言&#xff0c;ES6也就成为了必修课&#xff0c;所以本套课程先以ES6的新特性开始。 1.1、了解ES6 ES6&#xff0c;…

ES6新特性总结-面试必会

文章目录 一、let和const二、Symbol三、模板字符串3.1 什么是模板字符串3.2 字符串新方法 四、解构表达式4.1 数组解构4.2 对象解构 五、Set()、map()数据结构5.1 map()是什么及写法&#xff1f;5.1.1 map()是什么及写法&#xff1f;5.1.2 map()下的内置方法&#xff1a; 5.2 S…

JavaScript ES6新特性

JavaScript ES6带来了新的语法和特性&#xff0c;使得代码更加的现代和可读。它包括许多重要功能&#xff0c;如箭头函数、模板字符串、解构赋值等等。 const 和 let const 是 ES6 中用于声明变量的新关键字。const 比 var 强大。一旦使用&#xff0c;变量就不能重新分配。换…

ES6必须知道的六大新特性

ES6 ES6新特性-let&const 使用const表示常量&#xff08;声明之后不允许改变&#xff0c;一旦声明必须初始化&#xff0c;否则会报错&#xff09; //ES6 常量 不能修改const b2;b3;//Uncaught TypeError: Assignment to constant variable.console.log(b);使用var声明的…

ES6中有哪些新特性?

ES6中的新特性(一) ECMAScript6.0&#xff08;以下简称 ES6&#xff09;是 JavaScript 语言的下一代标准&#xff0c;已经在 2015 年 6 月正式发布了。它的目标&#xff0c;是使得 JavaScript 语言可以用来编写复杂的大型应用程序&#xff0c;成为企业级开发语言。 我们来看看…

面试题!es6新特性

es6新特性 ECMAScript 6(ES6) 目前基本成为业界标准&#xff0c;它的普及速度比 ES5 要快很多&#xff0c;主要原因是现代浏览器对 ES6的支持相当迅速&#xff0c;尤其是 Chrome 和 Firefox 浏览器&#xff0c;已经支持 ES6 中绝大多数的特性。 以下是一些常用到的es6新特性&…

es6的8条新特性总结

es6的8条新特性总结 认识es61.块级作用域变量&#xff08;let和const&#xff09;2.箭头函数3.模板字符串4.解构赋值5.默认参数6. 扩展运算符7. 类和继承8.Promise 认识es6 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript的新版本&#xff0c;引入了许多新特性和语法…

最全的—— ES6有哪些新特性?

目录 ES6新特性1、let和const2、symbol3、模板字符串3.1 字符串新方法&#xff08;补充&#xff09; 4、解构表达式4.1 数组解构4.2 对象解构 5、对象方面5.1 Map和Set5.1.1 Map5.1.2 Set 5.3 数组的新方法5.3.1 Array.from()方法5.3.2 includes()方法5.3.3 map()、filter() 方…

GWAS分析中协变量的区分(性别?PCA?初生重?)

1. 电子书领取 前几天发了一篇GWAS电子书分享&#xff0c;异常火爆&#xff0c;阅读量8000&#xff0c;很多人评价比较基础。这本电子书主要特点是比较基础&#xff0c;GLM模型用软件和R语言进行比较&#xff0c;如何添加数字协变量、因子协变量、PCA等内容&#xff0c;可以说…

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用

1.实验目的 此笔记本用作以下目的&#xff1a; 在多个时间序列上训练单个模型使用预训练模型获取训练期间未见的任何时间序列的预测使用协变量训练和使用模型 2.导库 # fix python path if working locally from utils import fix_pythonpath_if_working_locallyfix_python…

Mplus数据分析:随机截距交叉之后的做法和如何加协变量,写给粉丝

记得之前有写过如何用R做随机截距交叉滞后&#xff0c;有些粉丝完全是R小白&#xff0c;还是希望我用mplus做&#xff0c;今天就给大家写写如何用mplus做随机截距交叉滞后。 做之前我们需要知道一些Mplus的默认的设定&#xff1a; observed and latent exogenous variables a…

中介变量、调节变量与协变量

在平时看论文过程中偶会接触到这几个概念&#xff0c;然而都没想过弄明白&#xff0c;每次总觉得只要看明白个大概反正自己又不用这种方法…作为科研人&#xff0c;还是应该保持谦逊&#xff0c;保持学习 一、中介变量 1.概念 中介变量&#xff08;mediator&#xff09;是自…

协变量偏移/标签偏移/概念偏移

协变量偏移 这里我们假设&#xff0c;虽然输入的分布可能随时间而改变&#xff0c;但是标记函数&#xff0c;即条件分布P&#xff08;y∣x&#xff09;不会改变。虽然这个问题容易理解&#xff0c;但在实践中也容易忽视。 想想区分猫和狗的一个例子。我们的训练数据使用的是猫…

R语言绘制校正协变量后的ROC曲线

ROC曲线也叫受试者工作曲线&#xff0c;原来用在军事雷达中&#xff0c;后面广泛应用于医学统计中。ROC曲线是根据一系列不同的二分类方式(分界值或决定阈)&#xff0c;以真阳性率(灵敏度)为纵坐标&#xff0c;假阳性率(1-特异度)为横坐标绘制的曲线。 ROC曲线主要应用于二分类…