面试题android收藏

RecyclerView 回收复用

2019-08-16  本文已影响0人  灿烂呀

RecyclerView的缓存分为四级
Scrap
Cache
ViewCacheExtension
RecycledViewPool

1.Scrap对应ListView 的Active View,就是屏幕内的缓存数据,就是相当于换了个名字,可以直接拿来复用。(感觉就是临时存储,因为源码中貌似没有使用到)
2.Cache 刚刚移出屏幕的缓存数据,默认大小是2个,当其容量被充满同时又有新的数据添加的时候,会根据FIFO原则,把优先进入的缓存数据移出并放到下一级缓存中,然后再把新的数据添加进来。Cache里面的数据是干净的,也就是携带了原来的ViewHolder的所有数据信息,数据可以直接来拿来复用。需要注意的是,cache是根据position来寻找数据的,这个postion是根据第一个或者最后一个可见的item的position以及用户操作行为(上拉还是下拉)。
举个栗子:当前屏幕内第一个可见的item的position是1,用户进行了一个下拉操作,那么当前预测的position就相当于(1-1=0),也就是position=0的那个item要被拉回到屏幕,此时RecyclerView就从Cache里面找position=0的数据,如果找到了就直接拿来复用。
3.ViewCacheExtension是google留给开发者自己来自定义缓存的,这个ViewCacheExtension我个人建议还是要慎用,因为我扒拉扒拉网上其他的博客,没有找到对应的使用场景,而且这个类的api设计的也有些奇怪,只有一个public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position, int type);让开发者重写通过position和type拿到ViewHolder的方法,却没有提供如何产生ViewHolder或者管理ViewHolder的方法,给人一种只出不进的赶脚,还是那句话慎用。
4.RecycledViewPool刚才说了Cache默认的缓存数量是2个,当Cache缓存满了以后会根据FIFO(先进先出)的规则把Cache先缓存进去的ViewHolder移出并缓存到RecycledViewPool中,RecycledViewPool默认的缓存数量是5个。RecycledViewPool与Cache相比不同的是,从Cache里面移出的ViewHolder再存入RecycledViewPool之前ViewHolder的数据会被全部重置,相当于一个新的ViewHolder,而且Cache是根据position来获取ViewHolder,而RecycledViewPool是根据itemType获取的,如果没有重写getItemType()方法,itemType就是默认的。因为RecycledViewPool缓存的ViewHolder是全新的,所以取出来的时候需要走onBindViewHolder()方法。
再来张图看看整体流程


总结:
viewCache:高速缓存,可以拿来直接使用,不需要重新绑定数据。默认容量是2. 查找的规则是根据 position ,和id ,所以经常是差不到,基本上只有2个滑动的方向是相反的时候,会起到作用。
pool:缓存池,viewHolder 在进入pool 池前会先清除数据,所有复用这里的viewHolder 需要执行 onBindViewHolder()进行数据绑定。查询规则是 itemType,是一个 SparseArray保存的(SparseArray 类似于HashMap) ,key 是itemType ,pool 的数量是按照itemType 来区分的,每种类型默认保存5个。
viewCache (高速缓存),虽然是先去这里找但是这个找的方式是通过position 来找的,一定会去找,而不是说一定会找到。之前理解有误,以为一定会用到

例如:
现在总共有10调条数据,屏幕能显示5条(每行只有一条数据)
首次加载: onCreateViewHolder 会调用5次,全部数据都是新create ,这个时候 高速缓存,缓存池中都是 空的
向上滑动屏幕,加载新一条数据,这个时候缓存全是空,只能重新创建,这个时候position为0的数据会进入 高速缓存,高速缓存的容量,这个时候再次向上滑动加载新的一条数据,首先去高速缓存找,虽然高速缓存中有,但是因为position 不同,高速缓存里面存入的是position=0的数据,而我们新增的是position=6条数据,这个时候 pool中也没有可复用的,只能create ,position=1的数据进入高速缓存,(现在屏幕是2-6,高速缓存是0-1,pool 中没有)再次滑动,加载position=7数据,重复上面的过程,高速缓存中没position 为7的数据 而pool 中也没有缓存,所以继续create,这个时候position =2的数据进入高速缓存,position 为1 的数据进入 pool 中,同时数据被清空,即posititon 为1的数据已经不存在了(屏幕3-7,高速缓存 1-2,pool 中是0),pool 中此时有一个 viewHolder 但是没数据。再次滑动加载position=8数据,首先进入高速缓存,发现没有position =8的数据,没有找到,进入pool 中找,发现有ViewHolder 但是没有数据,这个时候不在create,因为 viewHolder 已有,只需要onbindViewHolder()重新绑定数据即可。
后续的滑动则不会再create 新的ViewHolder 了。所以供给创建了8 个ViewHolder 后续的操作就是如果高速缓存能够复用 则直接复用,不能复用则去Pool 中获取ViewHolder 对象,然后重新绑定数据。
情况2:
每行有5列,满屏是2行。相当于是屏幕默认的容量是10,与上面不同的就是每次加载量。
第一次向工滑动,这个时候缓存都为空,新建5个,第一行退出屏幕,这个时候屏幕是(5-9)高速缓存里是(0-1),pool 中是(2-4),已经创建了15次,这个时候继续向下滑动,首先去高速缓存里找,没有能找到对应position 的,继续pool 中找,发现有3个能复用,但是需要5个所以只能新建2个,到这里已经新建17个了。现在这个时候第2行的前两个数据(5-6)进入高速缓存,高速缓存中原有的数据和第二行剩下的3个数据进入pool 中,这个时候屏幕,高速缓存,poll 全部都填满了。这个时候在向下滑动的时候,就不会在重建了,因为即使高速缓存中找不到数据那pool 中一定是有足够数量的ViewHolder 的,只需要重新绑定数据即可。



(图片来源网络)
根据打印的log 通过 onBindViewHolder 的复用 positon 看到每一列在退出屏幕的时候进入高速缓存的是前2个,而非最后2个。
第三种情况就是 如果每列超过了5个
这个时候还是上面一样的机制,只不过这个时候pool 中最多有5个,所以如果每次向下滑动都需要新建2个吧,向上滑动如果告诉缓存中能被复用,则不需新建,否则应该也是要新建2个复用5个的。这种情况肯定是不好的嘛,所以尽可能不要超过五个.

总结:
一.复用与回收的顺序?
先复用在回收,道理很简单,如果是先回收,那高速缓存没意义了,因为如过时先回收,那高速缓存里的数据永远是刚刚退出屏幕的数据,不可能被服用到。
二.最多生成的viewHolder 数量?
分3种情况:
1.每行只有一列: 满屏数+高速缓存+1(因为每次只有新增1个,所以pooL中有一个备用足以)
2.每行是多列但是小于5 :屏幕数量+每一行的列数 +2,
3.如果每列大于5个的时候 :即使屏幕填满,高速缓存填满,pool 填满的话依然,仍然可能每次都要新建2个,除非高速缓存能复用。
三. 高速缓存可以设置数量,pool也可以自定义

补充:ListView
Listview的缓存机制
ListView的缓存有两级,在ListView里面有一个内部类 RecycleBin,RecycleBin有两个对象Active View和Scrap View来管理缓存,Active View是第一级,Scrap View是第二级。

Active View:是缓存在屏幕内的ItemView,当列表数据发生变化时,屏幕内的数据可以直接拿来复用,无须进行数据绑定。

Scrap view:缓存屏幕外的ItemView,这里所有的缓存的数据都是"脏的",也就是数据需要重新绑定,也就是说屏幕外的所有数据在进入屏幕的时候都要走一遍getView()方法。
再来一张图,看看ListView的缓存流程

ListView的缓存机制相对比较好理解,它只有两级缓存,一级缓存Active View是负责屏幕内的ItemView快速复用,而Scrap View是缓存屏幕外的数据,当该数据从屏幕外滑动到屏幕内的时候需要走一遍getView()方法。
生成的itemView 应该就是屏幕数量+每一行的列数,通常每行都是一个 就是 屏幕数+1,(满屏数量+1个缓存)当然如果是多列,或者多类型的就复杂了。比如一个多类型,第一次加载刚好显示的是类型1的数据,而开始向下滑动后,下一满屏都是另一个类型的,这个时候类型1应该全部进入缓存,数量就是满屏数量了吧。这个时候总共生成的数量反而比单一类型的少一个哈。

另外:滑动的时候一定是新的先出现哈,最顶部的退出一定是后于最底部新增的哈,只要top 让出一点距离,bottom 就进来了哈,只有当滑动停止的时候才会去判断边界,判断哪些该入缓存,这也是+1的原因而不是直接复用刚刚退出进入缓存的那个itemView。

参考:https://www.jianshu.com/p/3e9aa4bdaefd

上一篇 下一篇

猜你喜欢

热点阅读