Android踩坑经验--RecycleView Adapter缓存问题

项目中遇到一个问题,先看现象:
在这里插入图片描述
发现在一个屏幕上,会出现显示格式不一致问题,比较诡异,同一个布局文件,为什么绘制出来显示效果不一样呢?
RecycleView item的显示,主要是onCreateViewHolder和onBindViewHolder两个方法,看下方法调用情况:
在这里插入图片描述
发现在滑动的时候,并没有执行onCreateViewHolder,而是执行的onBindViewHolder,只有最后有一个item是“没有更多了 ”执行了onCreateViewHolder,那么滑动的时候应该是取的RecycleView的缓存。
但为什么缓存会出现时间的宽度不对的问题呢?
看下布局文件,发现:
在这里插入图片描述大胆猜测:如果宽度设置为0dp,子view没有重新measure,绘制时viewholder从缓存中获取,依然沿用了缓存中的width,而缓存中的width与此次onBindViewHolder中的数据长度不吻合,这才导致了上述情况。于是尝试将宽度设置为wrap_content,发现问题解决了。
RecycleView使用总结
在使用RecycleView时,需要对缓存有比较清晰的认识,最好能恢复View原来的属性,否则会出现混乱的情况。
在网上浏览时,也有类似case,比如holder有ImageView,点击后做属性动画,然后被RecycleView缓存起来,别的Item又拿来复用,虽然设置了图片,但ImageView已经做过属性动画,比如已经处于180旋转的状态,这时候显示就会有问题。
旋转之类的动画,可以用View动画,复杂的动画再用属性动画避免此问题,也可以重写Adapter的void onViewDetachedFromWindow(VH holder)方法,在里面拿到holder找到修改过的ImageView,恢复他原来的属性,避免出现显示混乱的问题
RecycleView缓存机制
疑问:复用的item取的是缓存?哪里的缓存?
答案:缓存来自RecycledViewPool,是RecycleView的四级缓存
RecyclerView的缓存机制,大致有3个类:Recycler、RecycledViewPool和ViewCacheExtension。
Recycler
用于管理已经废弃或者与RecyclerView分离的ViewHolder,为了方便理解这个类,整理了下面的资料,内部类的成员变量和他们的含义:
在这里插入图片描述
RecycledViewPool
RecycledViewPool类是用来缓存Item用,是一个ViewHolder的缓存池,如果多个RecyclerView之间用setRecycledViewPool(RecycledViewPool)设置同一个RecycledViewPool,他们就可以共享Item。其实RecycledViewPool的内部维护了一个Map,里面以不同的viewType为Key存储了各自对应的ViewHolder集合。可以通过提供的方法来修改内部缓存的Viewholder。
ViewCacheExtension
开发者可自定义的一层缓存,是虚拟类ViewCacheExtension的一个实例,开发者可实现方法getViewForPositionAndType(Recycler recycler, int position, int type)来实现自己的缓存。
Recyclerview的四级缓存
1.屏幕内缓存
屏幕内缓存指在屏幕中显示的ViewHolder,这些ViewHolder会缓存在mAttachedScrap、mChangedScrap中 :
mChangedScrap 表示数据已经改变的ViewHolder列表
mAttachedScrap 未与RecyclerView分离的ViewHolder列表
2.屏幕外缓存
当列表滑动出了屏幕时,ViewHolder会被缓存在 mCachedViews ,其大小由mViewCacheMax决定,默认DEFAULT_CACHE_SIZE为2,可通过Recyclerview.setItemViewCacheSize()动态设置。
3.自定义缓存
可以自己实现ViewCacheExtension类实现自定义缓存,可通过Recyclerview.setViewCacheExtension()设置。
4.缓存池
ViewHolder首先会缓存在 mCachedViews 中,当超过了个数(比如默认为2), 就会添加到 RecycledViewPool 中。RecycledViewPool 会根据每个ViewType把ViewHolder分别存储在不同的列表中,每个ViewType最多缓存DEFAULT_MAX_SCRAP = 5 个ViewHolder,如果RecycledViewPool没有被多个RecycledView共享,对于线性布局,每个ViewType最多只有一个缓存,如果是网格有多少行就缓存多少个。他们之间的关系如下 :
在这里插入图片描述
缓存策略
Recyclerview在获取ViewHolder时按四级缓存的顺序查找,如果没找到就创建。其中只有RecycledViewPool找到时才会调用 bindViewHolder,其它缓存不会重新bindViewHolder 。 流程如下 :
(从源码中看,感觉最开始会先从mChangedScrap查询(getChangedScrapViewForPosition方法),如果未找到再去mAttachedScrap,mCachedViews中查询(getScrapOrHiddenOrCachedHolderForPosition方法),建议还是阅读Android源码,在getViewForPosition()方法中基本就可知道缓存的运行机制,大致调用栈:getViewForPosition-> getChangedScrapViewForPosition()->getScrapOrHiddenOrCachedHolderForPosition->getViewForPositionAndType()->getRecycledViewPool().getRecycledView(type))
在这里插入图片描述
通过了解RecyclerView的四级缓存,我们可以知道,RecyclerView最多可以缓存 N(屏幕最多可显示的item数) + 2 (屏幕外的缓存) + 5*M (M代表M个ViewType,缓存池的缓存),只有RecycledViewPool找到时才会重新调用 bindViewHolder。
还需要注意的是,RecycledViewPool 可以被多个RecyclerView共享,其缓存个数与ViewType个数、布局相关,如果RecycledViewPool没有被多个RecycledView共享,对于线性布局,每个ViewType最多只有一个缓存,如果是网格布局有多少行就缓存多少个。

参考博客:https://zhooker.github.io/2017/08/14/关于Recyclerview的缓存机制的理解/
RecycleView稍有不慎,会有性能问题,关于性能优化有一幅图留作备忘:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/longlong2015/article/details/88825813