RecyclerView的回收复用机制

问题分析

RecyclerView是一个大的概念,从界面层次我们分析一下它的组成部分,它是由多个列表项组成的。那我们的研究对象从RecyclerView转到列表项Item上。

列表项Item有两个非常重要的内容,一个则是列表项的界面生成,这个和适配器中的onCreateViewHolder相关;另外一个则是列表项的数据绑定,这个和适配器中的onBindViewHolder相关。onCreateViewHolder是创建ViewHolder,在这个过程中我们会通过LayoutInflater去生成界面,而LayoutInflater是通过反射的方式实例化View的,基于这个情况可知创建ViewHolder是比较耗时的。onBindViewHolder是绑定相关的视图数据,这个过程中如果设置内容过多或者计算过多都会比较耗时。

如果我们RecyclerView的设计者,可以分析出onCreateViewHolder、onBindViewHolder这两个函数是比较耗时的情况下,我们会采取的策略就是减少调用,减少onCreateViewHolder和onBindViewHolder调用,达到性能最优的效果。

情况分析

如果当前情况下需要ViewHolder,又可以减少onCreateViewHolder的调用说明有同类型的缓存对象可供使用,可以不用重新创建,只需要对缓存对象重新绑定一下数据可以。

如果当前情况下需要ViewHolder,又可以减少对onBindViewHolder的调用说明有数据都不需要修改,可以直接拿过来用的缓存,这种缓存要么就是同位置的ViewHolder被回收了,要么就是同Id的ViewHolder被回收了。

缓存类型

根据上面的情况分析,我们可以得出缓存的两种类型:一种是同数据的、拿来就用、不需要做任何修改的缓存,这种类型的缓存包括Scrap、CacheViews;另外一种是同类的、拿过来之后需要重新绑定数据的缓存,这种类型的缓存是RecyclerViewPool,它给到每个Type(Type指的是适配器Adapter中的ItemType)的缓存容量默认是5。

使用场景

缓存的类型分析完后,我们就需要着重理解什么场景下需要用到缓存?那当然就是数据或者界面发生变化的时候,一类是进行滑动列表的时候,一部分Item需要离开屏幕,另外一部分Item需要进入屏幕,进入屏幕这部分Item就非常有可能是从缓存中读取的而来;另外一类就是主动的数据更新,比如通过notifyDataSetChanged更新所有Item数据、通过notifyItemChanged进行局部Item更新。

滑动

表项回收

在滑动的过程中是有item离开屏幕,有item进入屏幕,这个过程了就会涉及回收数据和读取缓存,两部分的内容,本文也是从这两个方面给大家讲解。

上图描绘的是RecyclerView的一个上滑的过程,左边用来表示一个RecyclerView的界面部分,橙色方框代表屏幕,上下两根横线分别代表着屏幕的上边界和下边界方便大家观察效果。右边是我们的缓存包括CacheViews和RecyclerViewPool两种。

当列表不断上滑的过程中了,列表项1会离开屏幕,表项如果离开屏幕就会加入到缓存,首先加入的就是CacheView缓存。

当列表项1和列表项2都加入了CacheViews并且列表项3都开始离开屏幕了,这个时候就会出现一个问题,CacheViews已经装满了,表项3又需要加入缓存,如何处理?其实很简单,最先进入的表项出去,新的进来,那么CacheViews里面装的就是表项3和表项2,表项1何处何从?它会安排进去RecyclerViewPool里面,不过这里会出现一个变化,那就是Item的数据失效了,如下图所示,所有加入RecyclerViewPool的缓存如果被读取了,都会需要重新绑定数据即会调用onBindViewHolder。

表项缓存读取

在不断向上滑动的过程中,有新的列表项进入屏幕,那新的列表项从何而来?有可能是通过onCreateViewHolder新创建的,也有可能是从缓存中读取而来。读取缓存的流程到底是如何的?我们细细探究一下。

在整个滑动的过程中,先读取的是CacheViews的缓存,那是任意一个缓存都可以?肯定不是,必须要符合条件,也就是会先匹配position(对应适配器里面的position),如果是同position缓存,说明正式需要寻找的数据。如果通过position没有寻找到数据,那就会通过id(对应适配器中getViewId中返回的id,默认是0)再寻找一遍,如果找到相同id的缓存则读取结束,没有读取到的话,就会在RecyclerViewPool中选中一个同类型(对应适配器中getViewType返回的Type)的缓存,RecyclerViewPool找到的缓存是需要重新绑定数据的。

如果RecyclerViewPool中也没有找到缓存,则会通过CreateViewHolder创建item,然后通过onBindViewHolder绑定数据,最后的这种方式就不属于读取缓存范畴,没有节省到时间。

Notify更新数据

NotifyDataSetChanged

如果调用notifyDataSetChanged这个api,那就说明主观意愿上需要重新绑定数据,表项Item的界面可以复用,但是表项展示的数据是不能用的。CacheViews这个缓存容器是用来保存不需要做任何修改的缓存,如果使用了它就不会调用onBindViewHolder绑定数据,所以不符合当前的需求,那么符合要求的只有RecyclerViewPool这个缓存容器里面来。

RecyclerViewPool对每个Type的缓存设置的大小是5,如果调用notifyDataSetChanged这个api,说明有五个Item会加入到RecyclerViewPool这一级的缓存里面来,然后在被读取重新赋予新的数据用于展示,这部分的Item是可以有效地回收复用。如果屏幕中展示的列表项Item大于五,则其他的一些列表项Item得不到及时的回收到缓存容器中,能复用但是又没利用上,而新的同位置的列表项因读取不到缓存而需要重新创建,相当于浪费了一部分的空间和性能,这种情况是非常不友好的。

如果一定要通过 notifyDataSetChange 方法更新数据,可以通过下面这种方式,在变更前调大缓存,变更完成后,调小缓存。这样布局变化也可以最大程度地复用已有的 ViewHolder。

mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 屏幕显示的item总数+1 );mAdapter.notifyDataSetChanged();new Handler().post(new Runnable() {    @Override    public void run() {        mRecyclerView.getRecycledViewPool()                .setMaxRecycledViews(0, 5);    }});复制代码

setMaxRecycledViews中填入的第一个参数是Type,你回收复用的列表项的Item,第二个参数是数目,一般是当前屏幕显示Item的总数加一,加一的原因是因为RecyclerView除了加载当前屏幕的Item以外了,还会额外再加载一个列表项。

之所以重新又将MaxRecycledViews的值设置回5,是因为缓存空间变大是满足当前notifyDataSetChange 的需求,后面的一些业务并不是缓存空间越大越好,调回来也是为了减少对后续操作的影响。

NotifyItemChanged

如何只需要更新一个列表项的话,我们可以调用notifyItemChanged,这样的话就只是局部更新,相对来说性能消耗会小很多。如果当前屏幕内有六个列表项Item1...Item6,我们点击其中的Item4,在这个过程中了,Item4的界面可以复用,数据需要重新绑定,Item1、Item2、Item5、Item6界面和数据都可以复用不需要做任何的改变,那RecyclerView是如何处理这个操作的呢?不着急我们慢慢讲,Item1、Item2、Item5、Item6会被加入到Scrap中的mAttachedScrap里面,需要更换数据的Item4会被加入到mChangedScrap缓存中,相信大家看完很清楚,不要变的放一起,需要变化下的又放另外一个容器。之后再通过比对position、id等方式读取出缓存的内容,进行相关的测量布局设置工作,那整个过程就讲清楚了。

总结

滑动过程和CacheViews、RecyclerViewPool有关,和Scrap缓存无关。Notify更新过程与Scrap、RecyclerViewPool有关,与CacheViews无关。

Scrap中的AttachedScrap、ChangedScrap没有大小限制,CacheViews默认是2,RecyclerViewPool默认大小是5。

读取缓存都先倾向于不需要做任何改变的缓存,读取过来直接用,通过Position、ID的方式比对,如果没有命中则去RecyclerViewPool中读取,这类的缓存不论如何都需要重新绑定数据。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容