文章目录
- 问题描述
- 缘由
- 解决办法 1:提高缓存容量
- 解决办法 2:每次在初始化视图数据之前重置视图数据
- 解决办法 3:优化设计,不在视图中储存数据
问题描述
安卓开发会有很多很多莫名其妙的坑。笔者在使用 RecyclerView 的过程中,发现一个很奇怪的事情。当 RecyclerView 有一项被点击之后,后面每间隔相同的一段都会有其它项被点击。经过笔者不断查看日志,这才发现问题所在。
笔者报错时的运行环境:
Android Studio Flamingo | 2022.2.1
Android SDK 33
Gradle 8.0.1
JDK 17
RecyclerView 1.2.1
缘由
原来是因为 RecyclerView 为了提高效率,于是使用缓存。然后后来在若干项之后,并没有创建新的视图 View,而直接循环复用了原来的视图,因此导致视图脏读,从而发生以上的问题。
具体来说,RecyclerView 要求适配器 RecyclerView.Adapter<?>
至少提供两个方法:onCreateViewHolder、onBindViewHolder。第一个方法用于创建视图,第二个方法用于初始化视图数据。问题在于,RecyclerView 在若干项之后,就不再调用 onCreateViewHolder,而是选择直接循环复用缓存中的视图数据,然后调用 onBindViewHolder 来进行初始化。
解决办法 1:提高缓存容量
很遗憾,安卓没有提供关闭缓存的 API。安卓很多组件都有一些错误设计,这种现象太普遍了,笔者早已习惯了。现实就是要在不良的环境下解决各种疑难杂症。
RecyclerView 支持设置缓存容量。因此,只需要将缓存容量设置为与数据项的数量一样即可。
recyclerView.setItemViewCacheSize(/* RecyclerView 列表数据项的数量 */);
不过,这种方法非常笨拙,有一些隐患。首先是性能的问题,这毋庸置疑。RecyclerView 复用视图本来就是为了提高性能。另一个问题是,如果向 RecyclerView 增加了一些项,此时需要同步变更缓存容量。如果删除了一些项,则缓存可能还会发生混乱。这是一个安全问题。
解决办法 2:每次在初始化视图数据之前重置视图数据
很遗憾,安卓同样没有提供一键重置视图 View 数据的 API,所以需要自己手动自定义代码重置。具体重置哪些数据呢?上次变更了更些数据,这次初始化视图数据之前(调用 onBindViewHolder 方法刚开始时)就要重置哪些数据。这种说法看起来很奇怪,为什么后面已经要初始化了,前面还要重置呢?这很好理解。因为一般只有不同视图数据不同的部分才需要初始化的,相同的数据是直接使用布局文件的默认值而不进行初始化。
比如,如果上次添加了鼠标点击按钮变色事件,那么这次就需要先把按钮颜色恢复至默认值。因为后面初始化数据的时候并不会再设定按钮的背景颜色。总之,上次变更了更些数据,这次初始化视图数据之前就要重置哪些数据。
不过,这种方法也比较笨拙,首先它耦合度很高,需要记住上次视图变化的每个细节。另外,这种方法也有安全问题。因为每次都重置视图数据,所以当滑动到下面的项时,前面视图的数据就被清空了。比方说,如果上次使得按钮变色,当滑动到下面的项,然后再滑回来时,前面那个按钮的颜色也会恢复原状。
解决办法 3:优化设计,不在视图中储存数据
已经知道,前面的 解决办法 1
和 解决办法 2
都各自有一些问题。这些问题的症结在于,RecyclerView 在设计的时候认为开发者不会在视图 View 中储存任何数据。因此,读者在开发的过程中需要区分,共性数据与个性化数据,视图数据空间与数据库数据空间。如果把个性化数据储存在视图数据空间,就会导致 bug。所以,最好的办法是不要在视图 View 中储存数据。
比如上面举例的点击按钮变色问题,不应该让视图 View 来储存这种颜色变化,而应该使用其它单独的数据结构来储存。这样设计起来就没有安全隐患。