RecyclerView 缓存及复用机制

RecyclerView 为什么要预布局

答案参考自:

什么是预布局

预布局是指在正式布局 RecyclerView 中的 ItemView 前执行的一次布局过程。

预布局的作用

预布局的作用是为了使 ItemAnimator 执行时能给用户更好的视觉体验。

预布局和正式布局的区别

预布局过程和正式布局过程执行的都是一样的代码,不同的是预布局过程得到的是 ItemAnimator 执行前的布局,而正式布局得到的是 ItemAnimator 执行后的布局也就是最终用户看到的布局。

什么情况下会执行预布局

当布局结束后若有新的 ItemView 在布局结尾显示则需要执行预布局,也就是当 RecycleView 中有 ItemView 被删除或更新时需要执行预布局。看下图更清晰。

RecyclerView 预布局

从上图中可以看到当不执行预布局时如果布局结尾有新的 ItemView 出现会执行 DefaultItemAnimator 的添加动画(淡入),这种看起来好像卡顿一样的显示给用户的感觉并不好。当然如果根本没有动画那预布局也就没有了意义。

RecyclerView的多级缓存机制,每一级缓存具体作用是什么,分别在什么场景下会用到哪些缓存

答案参考自:

RecyclerView 是先复用,后回收。

共四级缓存,分别为:

  1. ArrayList<ViewHolder> mAttachedScrap & ArrayList<ViewHolder> mChangedScrap (屏幕内)
  2. ArrayList<ViewHolder> mCachedViews (屏幕外)
  3. ViewCacheExtension mViewCacheExtension (自定义缓存)
  4. RecycledViewPool mRecyclerPool (缓存池)

每一级缓存的具体作用是什么?

  1. mAttachedScrap(屏幕内)
    1. 用于屏幕内itemview快速重用,不需要重新createView和bindView。
    2. 缓存大小没有限制,大小等于RecyclerView子View的数量。
    3. 该缓存中的ViewHolder无需重新绑定,只要ViewHolder的position和数据源中的position对应上。
  2. mCacheViews(屏幕外)
    1. 保存最近移出屏幕的ViewHolder,包含数据和position信息,复用时必须是相同位置的ViewHolder才能复用。
    2. 应用场景在那些需要来回滑动的列表中,当往回滑动时,能直接复用ViewHolder数据,不需要重新bindView。
    3. 缓存大小有限制,默认缓存大小为2,可以修改默认缓存大小。
    4. 该缓存的特性是FIFO。
    5. 该缓存中的ViewHolder无需重新绑定,只要ViewHolder的position和数据源中的position和itemType对应上。
  3. mViewCacheExtension(自定义缓存)。
    1. 不直接使用,需要用户自定义实现,默认不实现。
    2. 该接口只提供了get方法,没提供put方法。
  4. mRecyclerPool(缓存池)
    1. 当cacheView满了后或者adapter被更换,将cacheView中移出的ViewHolder放到Pool中,放之前会把ViewHolder数据清除掉,所以复用时需要重新bindView。需要重新执行onBindView的只有一种缓存区,就是缓存池mRecyclerPool。
    2. 每个itemType对应的ScrapData的缓存大小默认值是5,可以修改缓存大小。
    3. 该缓存中的ViewHolder需要重新绑定数据。
    4. 可以提供给多个RecyclerView共享。

RecyclerView的回收复用机制

RecyclerView滑动时会触发onTouchEvent#onMove,回收及复用ViewHolder在这里就会开始。

保存缓存流程:

  1. 插入或是删除itemView时,先把屏幕内的ViewHolder保存至AttachedScrap中。

  2. 滑动屏幕的时候,先消失的itemview会保存到CacheView,CacheView大小默认是2,超过数量的话按照先入先出原则,移出头部的itemview保存到RecyclerPool缓存池(如果有自定义缓存就会保存到自定义缓存里),RecyclerPool缓存池会按照itemview的itemtype进行保存,每个itemType缓存个数为5个,超过就会被回收。

获取缓存流程:

  1. AttachedScrap中获取,通过pos匹配holder——>获取失败,从CacheView中获取,也是通过pos获取holder缓存 ——>获取失败,从自定义缓存中获取缓存——>获取失败,从mRecyclerPool中获取 ——>获取失败,重新创建viewholder——createViewHolder并bindview。

  2. 总结一下上述流程:

    1. 通过mAttachedScrap、mCachedViews及mViewCacheExtension获取的ViewHolder不需要重新创建布局及绑定数据;
    2. 通过缓存池mRecyclerPool获取的ViewHolder不需要重新创建布局,但是需要重新绑定数据;
    3. 如果上述缓存中都没有获取到目标ViewHolder,那么就会回调Adapter#onCreateViewHolder创建布局,以及回调Adapter#onBindViewHolder来绑定数据。

ListView 和 RecyclerView 的区别

缓存机制

  1. ListView 是二级缓存,缓存的对象为 View
  2. RecyclerView 是四级缓存,缓存的对象为 RecyclerView.ViewHolder

使用方式

  1. ListView
    1. 需要继承 BaseAdapter 类
    2. 需要自定义 ViewHolder 实现 Item 的复用
  2. RecyclerView
    1. 需要继承 RecyclerView.Adapter 以及 RecyclerView.ViewHolder

布局效果

  1. ListView 只有一种纵向布局
  2. RecyclerView 默认有三种布局效果,可以自己继承 LayoutManager 实现自己想要的效果

空数据处理

  1. ListView 有 setEmptyView 方法处理 Adapter 数据为空的情况
  2. RecyclerView 必须自己处理数据为空的情况

Header View 与 Footer View

  1. ListView 提供了添加 Header View 与 Footer View 的方法
  2. RecyclerView 必须自己实现添加的方法

动画效果

  1. ListView 中没有默认实现的动画效果,但是我们可以在 Adapter 中自行实现。
  2. RecyclerView 中已经实现了部分默认的动画效果,例如 notifyItemChanged()notifyDataInserted()等方法。如果需要自定义动画,我们可以继承 RecyclerView.ItemAnimator类实现自己的动画效果。

点击事件

  1. ListView 实现了 onItemClickListener 接口。
  2. RecyclerView 中并没有实现默认的点击事件的监听方法,我们需要自行实现。

嵌套滚动机制

  1. ListView没有实现嵌套滚动机制。
  2. 在事件分发机制中,Touch事件在进行分发的时候,由父View向子View传递,一旦子View消费这个事件的话,那么接下来的事件分发的时候,父View将不接受,由子View进行处理;但是与Android的事件分发机制不同,嵌套滚动机制(Nested Scrolling)可以弥补这个不足,能让子View与父View同时处理这个Touch事件,主要实现在于NestedScrollingChild与NestedScrollingParent这两个接口;而在RecyclerView中,实现的是NestedScrollingChild,所以能实现嵌套滚动机制。

RecyclerView性能优化

答案参考自:

  1. 减少 xml 文件 inflate 时间

    xml文件包括:layout、drawable的xml,xml文件inflate出ItemView是通过耗时的IO操作。可以使用代码去生成布局,即new View()的方式。这种方式是比较麻烦,但是在布局太过复杂,或对性能要求比较高的时候可以使用。

  2. 设置高度固定

    如果item高度是固定的话,可以使用RecyclerView.setHasFixedSize(true);来避免requestLayout浪费资源。

  3. 共用RecycledViewPool

    在嵌套RecyclerView中,如果子RecyclerView具有相同的adapter,那么可以设置RecyclerView.setRecycledViewPool(pool)来共用一个RecycledViewPool。

    Note: 如果LayoutManager是LinearLayoutManager或其子类,需要手动开启这个特性:layout.setRecycleChildrenOnDetach(true)。

  4. 加大RecyclerView的缓存

    用空间换时间,来提高滚动的流畅性。

    recyclerView.setItemViewCacheSize(20);

    recyclerView.setDrawingCacheEnabled(true);

    recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);

  5. 增加RecyclerView预留的额外空间

    额外空间:显示范围之外,应该额外缓存的空间。

    1
    2
    3
    4
    5
    6
    newLinearLayoutManager( this {
    @Override
    protected int getExtraLayoutSpace(RecyclerView.Statestate) {
    return size;
        }
    });
  6. 减少ItemView监听器的创建

    对ItemView设置监听器,不要对每个item都创建一个监听器,而应该共用一个XxListener,然后根据ID来进行不同的操作,优化了对象的频繁创建带来的资源消耗。

  7. 回收资源

    通过重写 RecyclerView.onViewRecycled(holder) 来回收资源。


TODO

  1. RecyclerView的刷新回收复用机制
  2. RecyclerView的滑动回收复用机制

更多推荐阅读


RecyclerView 缓存及复用机制
https://luoyuy.top/posts/156f4346b4b7/
作者
LuoYu-Ying
发布于
2022年5月13日
许可协议