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和绑定数据的工作。最终思想就是,能不创建就不创建,能不重新绑定就不重新绑定,尽可能减少重复不必要的工作。

相关推荐
闲暇部落1 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX3 小时前
Android 分区相关介绍
android
大白要努力!4 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee4 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood4 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-7 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen10 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年17 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿19 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神21 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri