前言
最近一直在用 Kotlin
重写项目通用框架,并加上了完整的示例(项目已开源,还在不断持续更新中添加新功能,这是下载地址)。一来可以更好的学习 Kotlin
,再者还可以学习下 Android
的最新技术和最新版本的变化。其中在展示 RecyclerView
的常用功能时,关于重用机制又发现了一些需要注意的新东西,所以特此记录并分享下心得。
重用机制
强烈建议大家在阅读本文之前,先看一下我之前写的“RecyclerView ViewHolder 关于 Item 位置相关的不同属性的区别”。
本文主要是讲解 RecyclerView
的重用机制,对于常见用法大家可以自行参考开源项目中的示例。虽然已经有很多现成的轮子可供大家直接拿来用,但是依然有必要对基础知识加以学习。毕竟了解了内部原理才能更好的解决日后出现的各种问题。
这篇文章非常形象的图解了 RecyclerView
的重用机制,建议大家先看一下。
该图引自刚才推荐的文章,图中清楚的表明了 RecyclerView
重用机制是如何工作的。在阅读 RecyclerView
源码时,请特别重要 androidx.recyclerview.widget.RecyclerView.Recycler
这个类。主要的成员变量的默认值,都是在这个类里的。
我们平时接触最多的就是第一级缓存和第三级缓存,一级缓存大小默认值为 2
, 三级缓存按 type
区分,每个 type
的缓存默认值为 5
。
下面让我们来具体看一个实例:
上面这张图是一个非常简单的 RecyclerView
显示数据的例子。大家可以看到,屏幕可见区域内,显示了 7
条数据。再来看一下此时的 Log
:
通过日志可以看出,虽然屏幕上只显示了 7
条数据,但是实际 Adapter
却生成了 8
条数据,因为第 8
条数据是即将显示在屏幕上的。再来看下 Reuse tag
值都是 null
说明此时并没有任何 ViewHolder
被回收。
接着,我们向下滑动 RecyclerView
显示更多的数据,此时的日志如下:
我们可以看到,onViewRecycled
回调被调用了,说明有 ViewHolder
被回收了(Demo String1
,Demo String2
,Demo String3
, Demo String4
被回收了)。我们在被回收的 ViewHolder
上记录个 tag
(标记),这样当它被重用时,我们就知道重用的哪一个。
接下来,我们再向上滑动 RecyclerView
(也就是往回滑动),通过下面日志中的 tag
我们可以看到,当显示 Demo String3
,Demo String2
,Demo String1
这三条数据时,并没有重新生成新的 ViewHolder
,而是重用原来的 ViewHolder
。
我们再继续向下滑动时,虽然加载了更多的数据,但都是重用之前的 ViewHolder
,并没有再生成新的 ViewHolder
。如下图所示:
这就是 RecyclerView
显示高效的原因。
重用机制引来的问题“副作用”
重用机制虽然好,但是却会给我们带来一些小麻烦。想必大家再刚开始使用 RecyclerView
时一定都遇到过下面这样的问题:Item
的常见布局中,左侧一般都会显示一张图片,并且在 RecyclerView
进入编辑状态时,最左侧通常还会再显示一个 RadioButton
或 CheckBox
用于多选。当我们向下滑动数据时,左侧的图片会有被重复显示的现象;若我们选中一些 RadioButton
或 CheckBox
后,再向下滑动 RecyclerView
时,你会发现后面的一些 Item
也被莫名奇妙的选中了。这是为什么呢?
如果你仔细看过我刚才写的实例说明,那么你大致应该会明白为什么会有上面这些奇怪的问题了。没错,原因正是重用机制导致的。如果我们在代码层面不做一些特殊处理,只是简单的为 ViewHolder
赋值的话,肯定是会出上面的问题的。原因很简单,之前的实例也提到了,RecyclerView
会一直重用那些不再被显示的 ViewHolder
,并不会用每一条新数据创建一个新的 ViewHolder
。如果 Item1
的 CheckBox
之前是处于被选中状态的话,那么当它被重用后,自然也是处于被选中的状态,这就导致了会被“误选”的原因。图片会被“重复”显示也是一个道理,因为图片通常是被异步加载的,当之前已经被加载过图片的 ImageView
被重用时,若新的图片还没有被加载,就会出现旧的图片因为“重用机制”导致被“重复”显示的问题。
解决上述问题的办法也很多,大家 Google
一下很快就会解决这些问题。不过有一些做法表面上看虽然有效,但是可能会破坏“重用机制”,导致显示大幅性能,甚至还会导致 OOM
。由于这里有必要详细说一下。
解决“重用机制”的“副作用”
先说一下“RadioButton”或“CheckBox”被“误选”的解决办法。这里我不评论网上的做法哪些好,哪些坏,仅讲解笔者自己用到的解决办法。具体做法如下:在每个 Item
的数据 Bean
里添加一个“选中状态”变量,并在 onBindViewHolder
回调中根据“选中状态”的值来重新为“RadioButton”或“CheckBox”赋值。这样就可以解决“误选”问题,并且该方法也不会破坏“重用机制”。
上网查过资料的人可能会说:“有个更简单的办法,根据不需要添加‘变量’,只需要覆写‘getItemViewType(position: Int)’方法,直接返回 position
就可以解决这个问题”。没错,这样确实会解决这个问题,不光能解决这个问题,所有“重用机制”的“副作用”都可以被解决,这难道不是最优雅的解决办法吗?要是真这么简单就可以解决这个问题,Android
早就自行解决了。
看一下“getItemViewType(position: Int)”的注释就能明白,覆写该方法并返回 position
会直接破坏“重用机制”,导致无法 ViewHolder
无法被“重用”(该问题很容易被验证,覆写下该方法然后向之前实例那些打日志就可以验证该问题),因为每一个 Item
都有了一个唯一的 View Type
,显示 Item
时,就会为每一个 Item
生成一个新的 ViewHolder
,就这是表面上“副作用”被解决的原因,但这样也完美的破坏了“重用机制”,不能被重用,RecyclerView
的显示性能会大幅下降。当加载大量数据后,很容易就产生 OOM
。因此若要采取该方法解决“副作用”一定要注意加载的数据量不能过多。
再来说下图片被“重复”显示的问题。解决该问题方法很多,虽然也可以采用和刚才添加变量的方法来解决该问题,不过解决图片被“重复”显示的话,像刚才那么就显示有些麻烦了。无法是为 ImageView
添加 tag
,还是添加变量,本质上都是为了记一个状态,然后当有数据需要显示在页面上时,根据该变量的值,来判断是否需要重新设置新值。但是我们可以采取更加简单的方法:覆写“getItemId(position: Int)”方法。
不需要改动任何业务代码,仅通过覆写“getItemId(position: Int)”的方式就可以完美的解决图片被“重复显示”的问题,而且最重要的是这样做还不会破坏“重用机制”,导致完美的解决方案。
如何优化
直接上图说明解决办法。(点击此处查看原始图片来源)
源代码
需要源码的朋友,可以从我的 Gitee 上下载。