Android 基础技术——RecyclerView

笔者希望做一个系列,整理 Android 基础技术,本章是关于 RecyclerView

RecyclerView 对比 ListView 的优点
  • Adapter 面向的是 ViewHolder 不是 View, 可以省略 convertView.setTag 和 getTag 这些步骤
  • 可以设置布局管理器:竖向、横向、瀑布流方式
  • 可以设置 Item 的间隔样式
  • Recycleview去掉了一些api,比如setEmptyview,onItemClickListener等等,给到用户更多的自定义可能
  • Recycleview去掉了设置头部底部item的功能,专向通过viewholder的不同type实现
  • Recycleview实现了一些局部刷新 ,比如notifyitemchanged
  • Recycleview自带了一些布局变化的动画效果,也可以通过自定义ItemAnimator 类实现自定义动画效果
  • Recycleview缓存机制更全面 ,增加两级缓存,还支持自定义缓存逻辑
RecyclerView 一共有几级缓存

mAttachedScrap(屏幕内),mCacheViews(屏幕外),mViewCacheExtension(自定义缓存),mRecyclerPool(缓存池)

  • mAttachedScrap(屏幕内),用于屏幕内itemview快速重用 ,不需要重新createView和bindView
  • mCacheViews(屏幕外),保存最近移出屏幕的ViewHolder ,包含数据和 position 信息,复用时必须是相同位置的 ViewHolder 才能复用 ,应用场景在那些需要来回滑动的列表中,当往回滑动时,能直接复用ViewHolder数据,不需要重新bindView
  • mViewCacheExtension(自定义缓存),不直接使用,需要用户自定义实现,默认不实现。
  • mRecyclerPool(缓存池),当cacheView满了后或者adapter被更换,将cacheView中移出的ViewHolder放到Pool中,放之前会把ViewHolder数据清除掉,所以复用时需要重新bindView
RecyclerView 的缓存流程是怎样的
  • 保存缓存流程:
    • 插入或是删除itemView时,先把屏幕内的ViewHolder保存至AttachedScrap中
    • 滑动屏幕的时候,先消失的itemview会保存到CacheView ,CacheView大小默认是2,超过数量的话按照先入先出原则 ,移出头部的itemview保存到RecyclerPool缓存池(如果有自定义缓存就会保存到自定义缓存里),RecyclerPool缓存池会按照itemview的itemtype进行保存,每个itemType缓存个数为5个,超过就会被回收
  • 获取缓存流程:
    • AttachedScrap 中获取,通过pos匹配holder ------>获取失败,从CacheView 中获取,也是通过pos获取holder缓存 ------>获取失败,从自定义缓存中 获取缓存------>获取失败,从mRecyclerPool 中获取 ------>获取失败,重新创建viewholder------createViewHolder并bindview。
说下做过的RecyclerView 性能优化
  • bindViewHolder方法是在UI线程进行的,此方法不能耗时操作 ,不然将会影响滑动流畅性。比如进行日期的格式化。
  • 对于新增或删除的时候,可以使用diffutil进行局部刷新,少用全局刷新
  • 对于itemVIew进行布局优化 ,比如少嵌套等。
  • 加大RecyclerView缓存 ,比如cacheview大小默认为2,可以设置大点 ,用空间来换取时间,提高流畅度
  • 如果高度固定,可以设置setHasFixedSize(true)来避免requestLayout浪费资源 ,否则每次更新数据都会重新测量高度。
  • 如果多个RecycledView 的 Adapter 是一样的,比如嵌套的 RecyclerView 中存在一样的 Adapter,可以通过设置 RecyclerView.setRecycledViewPool(pool) 来共用一个 RecycledViewPool 。这样就减少了创建VIewholder的开销。
  • 在RecyclerView的元素比较高 ,一屏只能显示一个元素的时候,第一次滑动到第二个元素会卡顿。这种情况就可以通过设置额外的缓存空间,重写getExtraLayoutSpace方法 即可。
  • 设置RecyclerView.addOnScrollListener() 来在滑动过程中停止加载 的操作。
  • 减少对象的创建,比如设置监听事件,可以全局创建一个, 所有view公用一个listener ,并且放到CreateView里面去创建监听 ,因为CreateView调用要少于bindview。这样就减少了对象创建所造成的消耗
  • 用notifyDataSetChange时,适配器不知道整个数据集中的那些内容以及存在,再重新匹配ViewHolder时会花生闪烁。设置adapter.setHasStableIds(true) ,并 重写getItemId()来给每个Item一个唯一的ID,也就是唯一标识 ,就使itemview的焦点固定,解决了闪烁问题
RecyclerView 为什么可以做到局部刷新

RecyclerView的局部刷新就是依赖Scrap的临时缓存 ,当我们通过notifyItemRemoved(),notifyItemChanged()通知item发生变化的时候,通过mAttachedScrap缓存没有发生变化的ViewHolder,其他的则由mChangedScrap缓存 ,添加itemView的时候快速从里面取出,完成局部刷新。

注意,如果我们使用notifyDataSetChanged()来通知RecyclerView刷新 ,屏幕上的itemView被标记为FLAG_INVALID并且未被移除,所以不会使用Scrap缓存,而是直接扔到CacheView或者RecycledViewPool池中,回来的时候重新走一次绑定数据

注意:itemE并没有出现在屏幕中,它不属于Scrap管辖的范围,Scrap只会缓存在屏幕中已经加载出来的itemView的holder

RecycerView 如何缓存不同的itemType的ViewHolder
复制代码
    public static class RecycledViewPool {`
        `private static final int DEFAULT_MAX_SCRAP =` `5;`
`        static class ScrapData {`
            `final ArrayList<ViewHolder> mScrapHeap =` `new ArrayList<>();`
`            int mMaxScrap = DEFAULT_MAX_SCRAP;`
        `}`
`        SparseArray<ScrapData> mScrap =` `new SparseArray<>();`
    `}`

`

可以看出,RecycledViewPool中定义了SparseArray<ScrapData> mScrap ,它是一个根据不同itemType来保存静态类ScrapData对象的SparseArray,ScrapData中包含了ArrayList<ViewHolder> mScrapHeap ,mScrapHeap是保存该itemType类型下ViewHolder的ArrayList。

缓存池定义了默认的缓存大小DEFAULT_MAX_SCRAP = 5,这个数量不是说整个缓存池只能缓存这多个ViewHolder,而是不同itemType的ViewHolder的list的缓存数量,即mScrap的数量,说明最多只有5组不同类型的mScrapHeap 。mMaxScrap = DEFAULT_MAX_SCRAP说明每种不同类型的ViewHolder默认保存5个,当然mMaxScrap的值是可以设置的。这样RecycledViewPool就把不同ViewType的ViewHolder按类型分类缓存起来。

简述RecyclerView 的回收原理

同时也是能做到局部刷新的原理

在RecyclerView重新布局onLayoutChildren()或者填充布局fill()的时候,会先把必要的item与屏幕分离或者移除,并做好标记,保存到list中,在重新布局时,再将ViewHolde拿出来重新一个个放到新的位置上去。

(1)如果是RecyclerView不滚动情况下缓存(比如删除item),重新布局时 ,把屏幕上的ViewHolder与屏幕分离下来,存放到Scrap中,即发生改变的ViewHolder缓存到mChangedScrap中,不发生改变的ViewHolder存放到mAttachedScrap中 ;剩下ViewHolder的会按照mCachedViews>RecycledViewPool的优先级缓存到mCachedViews或者RecycledViewPool中。

(2)如果是RecyclerVIew滚动情况下缓存(比如滑动列表),在滑动时填充布局,先移除滑出屏幕的item,第一级缓存mCachedViews优先缓存这些ViewHolder,但是mCachedViews最大容量为2 ,当mCachedViews满了以后,会利用先进先出原则,把旧的ViewHolder存放到RecycledViewPool中后移除掉,腾出空间,再将新的ViewHolder添加到mCachedViews中,最后剩下的ViewHolder都会缓存到终极回收池RecycledViewPool中,它是根据itemType来缓存不同类型的ArrayList<ViewHolder>,最大容量为5。

简述 RecyclerView 的复用原理

RecyclerView 有5个缓存池子,mChangedScrap、mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool ,除了mViewCacheExtension是系统提供给开发者拓展的没有用到之外,还有四个池子是参与到复用流程中的。

当RecyclerView要拿一个复用的ViewHolder时,如果是预加载,则会先去mChangedScrap中精准查找(分别根据position和id)对应的ViewHolder,如果有就返回,如果没有就再去mAttachedScrap和mCachedViews中精确查找(先position后id)是不是原来的ViewHolder,如果是说明ViewHolder是刚刚被移除的,如果不是,则最终去mRecyclerPool找,如果itemType类型匹配对应的ViewHolder,那么返回实例,让它重新绑定数据,如果mRecyclerPool也没有返回ViewHolder才会调用createViewHolder()重新去创建一个。

这里需要注意:在mChangedScrap、mAttachedScrap、mCachedViews中拿到的ViewHolder都是精准匹配,但是mChangedScrap的是发生了变化的,需要调用onBindViewHolder()重新绑定数据, mAttachedScrap和mCachedViews没有发生变化,是直接使用的,不需要重新绑定数据 ,而mRecyclerPool中的ViewHolder的内容信息已经被抹除,需要重新绑定数据 。所以在RecyclerView来回滚动时,mCachedViews缓存池的使用效率最高。

总的来说:RecyclerView着重在两个场景缓存和回收的优化,一是:在数据更新时,使用Scrap进行局部更新 ,尽可能复用原来viewHolder,减少绑定数据的工作;二是:在滑动的时候,重复利用原来的ViewHolder,尽可能减少重复创建ViewHolder和绑定数据的工作。最终思想就是,能不创建就不创建,能不重新绑定就不重新绑定,尽可能减少重复不必要的工作。

相关推荐
流氓也是种气质 _Cookie12 分钟前
uniapp 在线更新应用
android·uniapp
zhangphil2 小时前
Android ValueAnimator ImageView animate() rotation,Kotlin
android·kotlin
徊忆羽菲3 小时前
CentOS7使用源码安装PHP8教程整理
android
编程、小哥哥4 小时前
python操作mysql
android·python
Couvrir洪荒猛兽4 小时前
Android实训十 数据存储和访问
android
五味香7 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
十二测试录7 小时前
【自动化测试】—— Appium使用保姆教程
android·经验分享·测试工具·程序人生·adb·appium·自动化
Couvrir洪荒猛兽9 小时前
Android实训九 数据存储和访问
android
aloneboyooo9 小时前
Android Studio安装配置
android·ide·android studio
Jacob程序员9 小时前
leaflet绘制室内平面图
android·开发语言·javascript