谈到 RecyclerView,相信不少同学,张口都能说出它的几级缓存机制:
例如:
- 一级缓存:mAttachedScrap 和 mChangedScrap
- 二级缓存:mCachedViews
- 三级缓存:ViewCacheExtension
- 四级缓存:RecycledViewPool
然后说怎么用,就是先从 1 级找,然后 2 级...然后4 级,找不到 create ViewHolder。
那么,有没有思考过,其实上面几级缓存都属于“内存缓存",那么这么分级肯定有一定区别。
问题来了:
- 每一级缓存具体作用是什么?
- 分别在什么场景下会用到哪些缓存呢?
更多问答 >>
-
每日一问 | 比 removeView 更轻量的操作,你了解过吗?
2020-07-27 01:14 -
每日一问| View 绘制的一个细节,如何修改 View 绘制的顺序?
2020-08-12 10:21 -
每日一问 | apply plugin: 'com.android.application' 背后发生了什么?
2020-08-16 19:56 -
2020-08-23 23:54
-
2020-08-26 21:11
-
2020-07-08 23:05
-
每日一问 | Android P 上,需要配置 network_security_config ,才能抓包,正确吗?
2020-06-29 21:26 -
每日一问 | 曾经的记忆中“onSaveInstanceState 会在系统意外杀死 Activity 时调用”,正确吗?
2020-07-12 23:49 -
2020-06-09 23:17
-
每日一问 | Activity与Fragment的那些事,“用起来没问题,我都要走了,你崩溃了?”
2020-06-22 00:39
先来温习一下RecyclerView的滚动和回收机制:
RecyclerView之所以能滚动,就是因为它在监听到手指滑动之后,不断地更新Item的位置,也就是反复layout
子View了,这部分工作由LayoutManager负责。LayoutManager在layout
子View之前,会先把RecyclerView的每个子View所对应的ViewHolder都放到mAttachedScrap
中,然后根据当前滑动距离,筛选出哪些Item需要layout
。获取子View对象,会通过getViewForPosition
方法来获取。这个方法就是题目中说的那样:先从mAttachedScrap
中找,再......Item布局完成之后,会对刚刚没有再次布局的Item进行缓存(回收),这个缓存分两种:
onBindViewHolder
方法)。这种缓存只适用于特定position
的Item(名花有主);onBindViewHolder
方法,好让对应Item的内容能正确显示。这种缓存适用所有同类型的Item(云英未嫁);第一种缓存,由Recycler.
如果回收的Item它的状态(包括:INVALID、REMOVED、UPDATE、POSITION_UNKNOWN)没有变更,就会放到mCachedViews
来保管,第二种放在RecycledViewPool中。mCachedViews
中,否则扔RecycledViewPool里。emmmm,除了滚动过程中,会对Item进行回收和重新布局,还有一种就是,当Adapter数据有更新时:
Inserted:如果刚好插入在屏幕可见范围内,会从RecycledViewPool中找一个相同类型的ViewHolder(找不到就create)来重新绑定数据并layout;
Removed:会把对应ViewHolder扔到
mAttachedScrap
中并播放动画,动画播放完毕后移到RecycledViewPool里;Changed:这种情况并不是大家所认为的:直接将这个ViewHolder传到Adapter的
onBindViewHolder
中重新绑定数据。而是先把旧的ViewHolder扔mChangedScrap
中,然后像Inserted那样从RecycledViewPool中找一个相同类型的ViewHolder来重新绑定数据。旧ViewHolder对象用来播放动画,动画播完,同样会移到RecycledViewPool里;注意:如果是使用
notifyDataSetChanged
方法来通知更新的话,那么所有Item都会直接扔RecycledViewPool中,然后逐个重新绑定数据的。好啦,温习完了之后,来看回题目中的问题:
这几个存放缓存的集合,各自的作用以及使用场景?mAttachedScrap
:LayoutManager每次layout
子View之前,那些已经添加到RecyclerView中的Item以及被删除的Item的临时存放地。使用场景就是RecyclerView滚动时、还有在可见范围内删除Item后用notifyItemRemoved
方法通知更新时;mChangedScrap
:作用:存放可见范围内有更新的Item。使用场景:可见范围内的Item有更新,并且使用notifyItemChanged
方法通知更新时;mCachedViews
:作用:存放滚动过程中没有被重新使用且状态无变化的那些旧Item。场景:滚动,prefetch;RecycledViewPool
:作用:缓存Item的最终站,用于保存那些Removed、Changed、以及mCachedViews满了之后更旧的Item。场景:Item被移除、Item有更新、滚动过程;写到这里发现漏讲了一个prefetch,好吧,这个prefetch机制就是RecyclerView在滚动和惯性滚动的时候,借助Handler来事先从RecycledViewPool中取出即将要显示的Item,随即扔到
mCachedViews
中,这样的话,当layout到这个Item时,就能直接拿来用而不用绑定数据了。为什么我没有说ViewCacheExtension?
因为我发现,这个东西我们开发者根本不能通过常规手段来使用!!!为什么这么说呢?星期五那晚特意网上搜了一下关于自定义ViewCacheExtension的文章,但是一篇相关的都没有,甚至官方的库也搜不到,Github上也搜过了,没有!本来我的想法是这样的:
看到大家的回答都没有针对ViewCacheExtension做解释,就想着根据自己的理解,补充一个自定义ViewCacheExtension的示例:但是这个抽象类它没有put
,只有get
,那就只能自己去获取缓存了,在哪里获取呢?想了一下,有两个地方比较合适(实际上是一个地方):onViewRecycled
方法;不过我们要做的这个缓存,并不打算缓存普通的Item,因为普通Item,现有的LayoutManager就已经做得很好了,我们应该用这个去缓存一些Bind比较耗时的,或者一些内容不会变(可以共享)Item。
emmm,理想很丰满,当我动手做时:哈哈哈哈哈,就是因为用了回收之后的Item,它的Scrap状态没有去掉,当再次被回收时,就报这个错了。
那RecyclerView内部是怎么处理的呢:在回调
这时候可能你会想到:在自定义的ViewCacheExtension取出来之前,手动把这些状态重置不就行了?没门!onBindViewHolder
之前,会重置这些状态标记。ViewHolder的
现在你可能会想:既然这样,那就干脆不用它回收之后的Item,直接用Adapter的setFlags
方法访问权限是default。createViewHolder
来事先创建不行吗?还真是不行,因为在取出View之后,会对它进行验证:如果这个View的LayoutParams的
那。。那我再手动赋值呢?不好意思,LayoutParams的mViewHolder
实例为空的话,还是会报错的。mViewHolder
访问权限也是default。也就是用常规方式(目前)是无法使用ViewCacheExtension了,想过用反射,但是,缓存这东西的目的就是要提高效率,为了能使用ViewCacheExtension而去做降低效率的事情,那就得不偿失了,除非你bind view的时间比反射所需时间多得多。
以后跟同学们谈论,说到ViewCacheExtension的时候,你就可以大声说:“别想了,这东西根本用不了的!Google弄出来这个就没想让我们用!”mChangedScrap这块,能再详细讲讲吗?不是特别明白
哪方面?具体作用还是使用场景?
就是临时缓存局部更新,用于播放动画,动画播放完viewholder还是会给 recyclerpool
看 ViewCacheExtension 的注释,有提到 @see LayoutManager#ignoreView(View) 这个方法,使用这个方法让默认的回收机制忽略 ...查看更多
看 ViewCacheExtension 的注释,有提到 @see LayoutManager#ignoreView(View) 这个方法,使用这个方法让默认的回收机制忽略
百度的一堆都是ViewCacheExtension实际使用的时候较少用到略过的。。。然后上谷歌找到了一篇应用的例子,这玩意儿可以这么来用 https://blog.csdn.net/Listron/a ...查看更多
百度的一堆都是ViewCacheExtension实际使用的时候较少用到略过的。。。然后上谷歌找到了一篇应用的例子,这玩意儿可以这么来用 https://blog.csdn.net/Listron/article/details/107952703
刚好上周看了这块源码- -
首先排除ViewCacheExtension,因为没使用过,且方法古怪(返回一个View??? 别的缓存都是返回Holder的阿)。按照缓存取的方式划分为两类: cache(通过index取) 和 recyclerPool(通过viewType取)Cache
这一类别的缓存只能通过index去取,但是被缓存的Holder的信息不会被reset。
mAttachedScrap/mChangedScrap
在每次layout()时,recyclerview会将当前children进行remove/detach,当我们开启stableId时进行的是detach操作,此时如果Holder发生了改变(notifyItemChanged/rangeChanged)那么就放入changeScrap中,反之放入到AttachScrap。
简单来说:
为什么每次布局前, 都要先detach/remove 所有的Holder?
不detach/remove没法布局呀哥哥们,例如: case : 页面上下两个view A和B ,A 是rv,B是任意view,然后B调用了requestLayout()引起整个视图树的layout。其一: 此时 layoutManager 知道holder没变化,但是不知道 “自身” 有没有变化(onMeasure时),例如B 大小变了,导致A也跟着变了,此时layoutManager就需要在当前可用的空间下重新测量,布局了,这能否解释: "为什么要先remove/detach 所有view"。
其二: 由于不remove/detach 会导致无法进行正确的测量以及之后的布局,所以recyclerview决定每次测量,布局时先将view remove/detach,但是这些view所对应的holder如果没有变化那么直接丢弃有点可惜,放入到recyclerpool呢?也有点不好,因为池子的特性导致再次复用时需要重新bind。 所以又单独创建Scrap缓存。
mCachedViews
缓存的是离屏Holder,且当开启预取机制时,预取的Holder也会放到这个缓存集中,其默认容量是2,但开启预取时令说。
等等,离屏Holder不应该是被放入到RecyclerPool吗?还真不一定,我们看下源码:两个条件:
当上面这两个条件不满足,那么这个Holder就会被reset且回收到RecyclerPool中。
RecyclerPool
从这里面取出的Holder会re bind,且智能通过viewType查找,当没有stableId时且调用了notifyDataSetChange()时页面上所有的Holder都会被添加到pool中。
内部结构如下:一个ScrapData代表一种ViewType的Holder缓存集,默认大小是5,容量满则停止放入,但是从pool中取成功时,会将这个holder从对应viewType的缓存集中移除。
1级缓存:缓存页面上展示出来的ViewHolder,在执行notify的时候会把页面上的缓存起来,变化的放到mChangedScrap中,其余放到mAttachedScrap ,在填充布局的时候,把attache 中的直接取出来使用,mChangedScrap中的会转到RecycledViewPool中,重新bind 数据。
2.二级缓存:缓存移出屏幕外的ViewHolder,默认长度是2,遵循的FIFO的原则,在二级缓存中的ViewHolder,直接拿出来展示即可。当缓存超出时,从mCachedViews 中的 移出的最先放入的ViewHolder ,放到RecycledViewPool中去。3.三级缓存:RecyclerView 没有自己去使用三级缓存,是留给用户去自定义扩展缓存,需要用户自己管理创建和缓存。4.从mCachedViews中存不下的会转移到RecycledViewPool中,在滑动列表从RecycledViewPool数据再取出复用的时候会按照Type 来查找,如果找到,执行bind 重新绑定数据,默认RecycledViewPool的容量是5.应用场景:
mAttachedScrap 和 mChangedScrap:执行notifyxx 在layout布局的时候,会把页面中的ViewHolder 存起来,然后再取出来用。
mCachedViews: 页面滑动时,缓存最后移出屏幕的2个ViewHolder,如果页面经常上下来回滑,可增大mCachedViews 的长度来做优化。
RecycledViewPool:滑动列表,如果不满足前面的缓存复用,在RecycledViewPool中有数据,取出来执行bind操作。 如果notify频繁,并且有多较多中type的时候,也可以考虑适当增大RecycledViewPool的长度。
如果以上缓存没有,则会执行 create与bind操作。
不解释,我这里写了一篇文章,已经非常详细了:https://blog.csdn.net/qw85525006/article/details/91127988
包含 滚动,更新,每个缓存的用处,优化点等等
mCachedViews 主要的作用还是为了避免内存抖动的.
ViewCacheExtension 扩展自定义,一般很少使用,RecycledViewPool 就是比较常使用的,具体他们之间有什么区别,就看文章吧.1.
mAttachedScrap用于itemview布局排列,比如最初显示,和用于itemView的重新排列,比如item的拖拽移动。
mChangedScrap则是用于局部更新。
mCachedViews用于滑动时隐藏的itemview的缓存和显示的itemview的复用ViewCacheExtension,自定义缓存RecycledViewPool,mCachedViews的容量只有2,满了之后就把最旧的给RecycledViewPool,新的给自己。2。 mAttachedScrap:布局初始化和重新排列
mCachedViews :滑动时
ViewCacheExtension:mCachedViews满了,如果ViewCacheExtension实现了,则把最旧给其缓存
RecycledViewPool:ViewCacheExtension满了或没有实现,mCachedViews满了,则把最旧给RecycledViewPool缓存