likes
comments
collection
share

【Android面试题】高级UI面试题——RecyclerView的回收复用机制

作者站长头像
站长
· 阅读数 74

RecyclerView的回收复用机制

这道题想考察什么?

考察同学是否对RecyclerView的Recycler熟悉。

考生应该如何回答

问题分析

​ RecyclerView是一个大的概念,从界面层次分析一下它的组成部分,它是由多个Item。Item的创建、赋值、回收、复用与RecyclerView息息相关,所以列表项是研究RecyclerView研究的重点切入口。

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

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

情况分析

​ 如果当前需要创建一个列表项Item,可以选择onCreateViewHolder创建,也可以选择先去读取缓存。根据缓存的界面、数据的不同,会存在两种情况:第一种情况,缓存中有与需要创建列表项item同类型(适配器中的ViewType)的缓存,那么就不需要重新创建,也就是减少了onCreateViewHolder的调用。第二种情况,如果存在一种缓存,它与需要创建的列表项Item类型相同,并且数据相同,那么就不需要重新绑定数据,更不需要重新创建,也就是同时减少onCreateViewHolder、onBindViewHolder的调用。

缓存类型

​ 据上面的情况分析可以得出缓存的两种类型:第一类是与需要创建Item的类型相同但数据不相同,这种类型的缓存是RecycledViewPool,它给到每个Type(Type指的是适配器Adapter中的ItemType)的缓存容量默认是5。第二类是与需要创建列表项Item的类型相同并且数据相同,这种类型的缓存包括Scrap、CacheViews;

使用场景

​ 当RecyclerView的数据或者界面发生变化的时候,就会用到缓存,大体分为两类情况,一类是进行滑动列表的时候,另外一类就是主动的数据更新。滑动列表的啥时候,一部分Item需要离开屏幕,另外一部分Item需要进入屏幕,进入屏幕这部分Item就非常有可能是从缓存中读取的而来。 主动更新数据,比如通过notifyDataSetChanged更新所有Item数据、通过notifyItemChanged进行局部Item更新,会将数据先放置到缓存容器中,然后在进行相关的更新操作。

滑动

Item回收

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

【Android面试题】高级UI面试题——RecyclerView的回收复用机制

图7.32.1

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

【Android面试题】高级UI面试题——RecyclerView的回收复用机制

图7.32.2

​ 如图7.32.2所示,当列表不断上滑的过程中了,Item1会离开屏幕,Item如果离开屏幕就会加入到缓存,首先加入的就是图7.32.2左边的CacheViews缓存。 【Android面试题】高级UI面试题——RecyclerView的回收复用机制

图7.32.3

​ 如图7.32.3所示,当列表项Item3离开屏幕时,CacheViews保存着Item1和Item2。这个时候就会出现一个问题,CacheViews已经装满了,Item3又需要加入这个容器中来,如何处理?其实很简单,新的加入旧的出局,那么CacheViews里面装的就是Item3和Item2。Item1何处何从?它会安排进去RecycledViewPool里面,不过这里会出现一个变化,那就是Item的数据失效了。如下图7.32.4所示,所有加入RecycledViewPool的缓存如果被读取了,都会需要重新绑定数据即会调用onBindViewHolder。 【Android面试题】高级UI面试题——RecyclerView的回收复用机制

图7.32.4

Item缓存读取

​ 在不断向上滑动的过程中,有新的Item进入屏幕,那新的列表项从何而来?有可能是通过onCreateViewHolder新创建的,也有可能是从缓存中读取而来。

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

【Android面试题】高级UI面试题——RecyclerView的回收复用机制

图7.32.5

​ 如果RecyclerViewPool中也没有找到缓存,则只能通过CreateViewHolder创建item,然后通过onBindViewHolder绑定数据。

Notify更新数据

NotifyDataSetChanged

​ 如果调用notifyDataSetChanged这个api,这个函数调用的结果是更新界面上所有item的显示内容。这个时候由于复用机制的存在,所以表项Item的界面(View本身)可以复用,但是Item展示的数据是需要重新更新的。在上述过程中Item会先加入缓存,然后再从缓存中读取出来重新绑定数据,符合要求的缓存容器只有RecycledViewPool,因为它保存的Item是需要重新绑定数据的,符合当前的刷新要求。

​ RecycledViewPool对每个Type的缓存设置的大小是5。如果调用notifyDataSetChanged这个api,说明有五个Item会加入到RecycledViewPool这一级的缓存里面来,然后在被读取重新赋予新的数据用于展示,这部分的Item是可以有效地回收复用。如果屏幕中展示的列表项Item大于五,由于缓存容器大小的缘故,只能有五个Item可以加入到缓存容器RecycledViewPool,可以回收复用,其余的部分Item由于不能及时加入缓存容器RecycledViewPool,那么它的空间就不会被回收复用,导致空间的浪费,导致之后需要重新创建。

​ 如果一定要通过 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的总数加一,加一的原因是因为RecyclerView除了加载当前屏幕的Item以外了,还会额外再加载一个Item。

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

NotifyItemChanged

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

总结

  1. AttachedScrap、ChangedScrap只与Notify更新数据有关,与滑动过程的回收无关,二者的空间是没有限定大小的。在一次Notify更新的过程中不需要发生变化的列表项Item会存放在AttachedScrap内,需要更新的列表项Item会放置在ChangedScrap,所以AttachedScrap、ChangedScrap被称之为屏幕内的缓存。
  2. CacheViews是用于保存最新被移除(remove)的列表项Item,一般指的是滑动过程中离开屏幕的列表项Item,会精准的通过位置Position、ID匹配当前缓存是否为需要的内容。因为是精准匹配,所以读取出来的缓存是不需要重新绑定数据的。
  3. RecycledViewPool是一个终极回收站,真正存放着被标识废弃(其他池都不愿意回收)的ViewHolder的缓存池,如果上述AttachedScrap、ChangedScrap、CachedViews都找不到ViewHolder的情况下,就会从RecycledViewPool返回一个废弃的ViewHolder实例。

详细关注公众号:Android老皮 还能解锁  《Android十大板块文档》 ,让学习更贴近未来实战。已形成PDF版

内容如下

1.Android车载应用开发系统学习指南(附项目实战) 2.Android Framework学习指南,助力成为系统级开发高手 3.2023最新Android中高级面试题汇总+解析,告别零offer 4.企业级Android音视频开发学习路线+项目实战(附源码) 5.Android Jetpack从入门到精通,构建高质量UI界面 6.Flutter技术解析与实战,跨平台首要之选 7.Kotlin从入门到实战,全方面提升架构基础 8.高级Android插件化与组件化(含实战教程和源码) 9.Android 性能优化实战+360°全方面性能调优 10.Android零基础入门到精通,高手进阶之路

转载自:https://juejin.cn/post/7270167187658358795
评论
请登录