玛卡巴卡的卡巴卡玛 发表于 2025-3-21 00:04:04

RecyclerView的缓存机制(面试常客)

在构建滚动列表时,我们常首选RecyclerView,出于它良好的缓存复用机制。
核心机制

RecyclerView的缓存机制又称回收复用机制,RecyclerView构建列表视图分为以下三步:
https://i-blog.csdnimg.cn/direct/259faa8bf04c45819de8cf52f7bb7ed8.png
 第一步的创建ViewHolder是RecyclerView构建视图时最耗时的操作,而RecyclerView正是因为做到了缓存复用,从而减少调用创建ViewHolder的次数。
当列表被创建时结构如下,每个ViewHolder中的A、B代表差别的View种类(ViewType):
https://i-blog.csdnimg.cn/direct/5fe8a52c6f1f4d86a42bcec33ed1ecb9.png
当我们滚动第一个ViewHolder(此处简称p0)到屏幕以外时,它会被存入mCachedViews中:
https://i-blog.csdnimg.cn/direct/1d37ee1f9b4c4559a9a4199bf664b250.png
此处被回收的p0依旧保存着之前的数据,各种内部状态没有被重置。
假如我们向上滚动,被缓存的p0会从mCachedViews缓存区中移回列表:
https://i-blog.csdnimg.cn/direct/4674b173838545f8968c00684fe9570d.png 
因此不需要重复CreateViewHolder和BindViewHolder,减少了耗时。
可见,缓存区是用来存储最近脱离屏幕区的ViewHolder。 这些ViewHolder因为出于屏幕区的界限,更容易因为用户的滚动和抖动被重新显示,因此更需要被快速的加载到视图中。
缓存区存储ViewHolder的默认大小为2,开发者可根据实际需求更改大小。
当缓存区已满,而有新的ViewHolder被放入缓存区时,最先被置入缓存区的ViewHolder会被移到mRecyclerPool中:
https://i-blog.csdnimg.cn/direct/8b1cdb7e869b4a89a9fc8edc3e95e6b1.png
进入mRecyclerPool的ViewHolder各种内部状态会被重置,如position会被置为-1,可理解为与之前的数据已解绑;图中的A和B表示差别的ViewType,每个能存储5个ViewHolder,这个大小也是可以被修改的。
分析完列表移出ViewHolder的过程,我们反过来看移入ViewHolder的:
https://i-blog.csdnimg.cn/direct/e72d816c73814c6b8071bcc4c04901f8.png
假设此时有一个新的范例为B的ViewHolder(简称p7)要移入屏幕区。
起首查看缓存区中是否有可以复用的ViewHolder。这是因为被放入此处的ViewHolder是不需要重置数据的,因此优先思量这里的ViewHolder,假如有相同的position,就会被取出复用。此处的逻辑和前面p0重新移入的逻辑相同;
图中的例子position=7,与缓存区中的两个ViewHolder的position都不相同,因此无法复用;接下来查看循环池中的ViewHolder。因为View范例为B的循环池中没有ViewHolder,所以依然无法复用;此时我们只能重新走Create和Bind的方法。
假如循环池中有相同范例的ViewHolder:
https://i-blog.csdnimg.cn/direct/36e68b7729334e80bfd2594c40eec3c1.png
会从循环池中取出复用,因为被放入循环池中的ViewHolder都是被重置了的,虽然不消走Create方法,但是重新的Bind还是要有的。
是否可以只用一种ViewType?

对于差别的ViewType我们有差别的循环池,那我们是否可以只用一种ViewType,延伸这一个循环池,提高回收复用率?
起首是可读性和可维护性会变差。因为差别的ViewType通常数据结构和视图样式方式差别,假如为了让一种ViewType都适配他们,代码肯定会变得很乱。
其次是不消懒加载,增加调用耗时;使用懒加载,无法克制视图切换耗时。起首因为ViewType的数据结构和视图样式等诸多内容不尽相同,假如一次全部加载内存开销必然很大,因此我们使用懒加载的方式最为合适,只有需要的时间才取出使用;
但因为我们把原本应该设计为差别的ViewType都塞进了一个ViewType,导致会一次加载所有原本差别的ViewType,无法使用懒加载克制这一征象;
而同时因为ViewType视图样式差别,肯定要重新inflate差别的视图样式,根本没有减少耗时。
RecyclerView缓存机制失效的环境

重写Adapter的onFailedToRecycleView

起首查看RecyclerView的源码:
https://i-blog.csdnimg.cn/direct/867b5fa82e87472caad3cb93befea0de.png
在是否回收ViewHolder的判断中包罗两个条件:boolean值forceRecycle和isRecyclable方法。我们起首查看后者代码:
https://i-blog.csdnimg.cn/direct/20cad6cf0f8a42b6b14fc4384de203b1.png
后者的isRecyclable方法获取到的是一个名为TransientState的值(直译:临时状态)。当某个ViewHolder中的某个View在做属性动画时,这个值会属性动画开始和竣事时被设置:
https://i-blog.csdnimg.cn/direct/79c8a1485b7e4210bd90d2ae812963ec.png 
这么来说,假如一个ViewHolder在脱离显示区时,同时内部有恣意一个view在做属性动画,TransientState的值就会是false,isRecyclable方法返回的也是false,这个ViewHolder就无法被存储到缓存区或循环池中。
这种环境最容易出现在ViewHolder中存在循环属性动画的场景。假如没有对这个循环属性动画做处理,那么就会出现缓存机制失效的环境。如dy中的专辑旋转动画:
https://i-blog.csdnimg.cn/direct/49b42160bf034110b36a7f45792af90f.png
而是否回收ViewHolder的判断中,前者forceRecycle是根据onFailedToRecycleView方法设定的,我们可以重写这个方法:
override fun onFailedToRecycleView(holder: MyViewHolder?): Boolean {
    return true
} onBindViewHolder中重置View的动画属性

为什么Android要设计ViewHolder中的View的属性动画不能被回收,要我们如此大费周章?
这么设计的原因是防止ViewHolder被复用时之前的动画状态被保存。因此我们可以在重新绑定ViewHolder的onBindViewHolder方法中,重置View的动画属性,如偏转角度(Rotation)、透明度(Alpha)等。
   RecyclerView的特殊复用机制
循环池(mRecyclerPool)可以服务于多个RecyclerView。当其他RecyclerView中拥有循环池中相同的ViewType时,他们都可以使用这个循环池。
参考 

Android RecyclerView的缓存机制_哔哩哔哩_bilibili

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: RecyclerView的缓存机制(面试常客)