浅谈RecyclerView缓存

此篇是浅谈,后续会发布相关源码分析

RecyclerView 是一种在 Android 开发中用于高效展示大量数据列表的视图容器,(ios类似的组件:UICollectionView

缓存的使用过程

当LayoutManager请求RecyclerView提供某个位置的View时,RecyclerView会按以下顺序查找:

  1. 已变更的临时缓存(Changed Scrap) (用于处理数据变更时的动画,如notifyItemChanged()
  2. 已附加的临时缓存(Attached Scrap) (屏幕内尚未完全分离的ViewHolder)
  3. 未移除的隐藏视图 (通过mChildHelper查找隐藏但未移除的View)
  4. 视图缓存(View Cache) (屏幕外已滑出的ViewHolder,默认缓存2个)
  5. 通过稳定ID二次搜索 (若适配器支持稳定ID,会按ID在缓存中精确匹配)
  6. 自定义缓存扩展(ViewCacheExtension) (开发者可自定义缓存策略,但极少使用)
  7. 全局回收池(RecycledViewPool) (跨RecyclerView共享的缓存池,每个ViewType最多存5个)

若上述均失败,调用onCreateViewHolder()创建新实例,必要时通过onBindViewHolder()绑定数据。

缓存层级设计

RecyclerView采用四级缓存机制:

  • 一级缓存(临时缓存):mChangedScrap(动画相关)、mAttachedScrap(屏幕内未分离的ViewHolder)。
  • 二级缓存(滑出屏幕的ViewHolder):mCachedViews,默认容量为2,快速复用最近滑出的Item。
  • 三级缓存(自定义扩展):ViewCacheExtension,开发者可自定义策略(如按特定条件缓存)。
  • 四级缓存(全局池):RecycledViewPool,跨RecyclerView共享,需注意类型(ViewType)隔离

屏幕上显示的视图(A Views)

↓ 滑出屏幕

一级缓存:Scrap / mAttachedScrap

↓ 布局结束后

二级缓存:Cache / mCachedViews

↓ 空间不足 / 不需要暂存(没有设置自定义缓存的情况)

四级缓存:RecycledViewPool

(iOS相对应的view是叫:UITableView和UICollectionView)

RecycledViewPool

介绍

RecycledViewPool 是 RecyclerView.Recycler 的一个内部类:

csharp 复制代码
public static class RecycledViewPool { 
private SparseArray<ArrayList<ViewHolder>> mScrap = 
  new SparseArray<>(); 
  private SparseIntArray mMaxScrap = new SparseIntArray();   
       ... 
    public ViewHolder getRecycledView(int viewType) { 
ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);

每种视图类型都有自己的容量,默认值为 5,但是这个数值可以通过在代码中动态改变。如果你知道某些视图类型的项目非常罕见,每次出现的数量都不会超过一个(比如一个页面的某个首图肯定只有一个),那就应该将该视图类型的项目池大小设置为 1。

且多个RecyclerView可以共同复用同一个RecycledViewPool

应用场景

  1. 滚动过程中,视图超出了 RecyclerView 的边界。

如果 mCachedViews 还有空位会优先放入其中,然后才放入 RecycledViewPool

  1. 数据已更改,因此视图不再可见。当消失动画结束后,才会向池中添加新元素。

比如:某 item 被删除或位置变化,Recycler 启动 item disappear animation(消失动画), 回收时再遵循本地缓存优先、缓存满则入全局池的规则。。

  1. 视图缓存中的某个项目已被更新或删除。
  2. 在查找 ViewHolder 时,我们在缓存或资源中找到了一个位置符合要求的 ViewHolder,但由于视图类型或 ID 错误(如果适配器具有stable ID),该 ViewHolder 并不适用

比如:Recycler 在复用时会按 view type(getItemViewType)和(若启用)stable ID 来查找合适的 ViewHolder。如果找到的 holder 在类型或 ID 上不匹配,就不能直接复用。当找到但不适用的情况下,Recycler 会 把那个不适用的 ViewHolder 回收(即从缓存/候选列表移除) ,随后按回收策略放入 mCachedViews(若有空间且合适)或放入 RecycledViewPool。特别是如果启用了 stable IDs 并且发现 ID 不匹配,Recycler 不能冒然复用以避免错位显示,因此会把该 holder 当作"无用"回收掉。开启 stable ID 会让 Recycler 更敢复用

ID 不匹配会强制回收,不会复用,Stable ID 能减少闪烁和错位,

适合作为"唯一身份",不能随意变化

  1. 布局管理器在布局前添加了一个视图,但在布局后没有添加该视图。比如一些 LayoutManager(或预取/预布局机制)会在 layout/预布局阶段临时 add 一个 view(用于测量或预取),但最终决定不将它作为实际 child 保留。

LayoutManager 决定要不要,Recycler 去缓存里拿,拿不到让 Adapter 造 ViewHolder,回收的进缓存或池子。

每种视图类型的池都采用栈式结构(后进先出)。

当我们向下滚动时,当前显示的项目后面会有一段"尾巴",由缓存的项目和一个池化项目组成。当项目 8 出现在屏幕上时,缓存中找不到合适的 ViewHolder:没有与位置 8 关联的 ViewHolder。因此,我们使用之前位于位置 3 的池化 ViewHolder。当顶部的项目 6 消失时,它会被放入缓存,并将项目 4 推入池中。

当我们开始反方向滚动时,画面就不同了:

ViewCacheExtension

介绍

The idea of ViewCacheExtension is attractive: it's a cache that behaves the way you want. ViewCacheExtension 的理念很有吸引力:它是一个可以按照你期望的方式运行的缓存。

除非你通过setViewCacheExtension()RecyclerView 的方法显式设置它并实现一个简单的接口,否则它不会存在。

应用场景

  • 你有少量"特殊项"(如广告),位置固定、内容稳定、不怎么变化。
  • 这些项分散在列表里,来回滚动时通常会用一个 ViewHolder 在它们之间反复重绑( onBindViewHolder ),造成卡顿。
  • 你希望为每个特殊项保留自己的视图实例,避免重复重绑,提高滚动流畅度。
scala 复制代码
SparseArray<View> specials = new SparseArray<>();
...
//防止将该特殊viewholder放入缓存池中
recyclerView.getRecycledViewPool().setMaxRecycledViews(SPECIAL, 0);

recyclerView.setViewCacheExtension(new RecyclerView.ViewCacheExtension() {
   @Override
   public View getViewForPositionAndType(RecyclerView.Recycler recycler,
                                         int position, int type) {
       return type == SPECIAL ? specials.get(position) : null;
   }
});

...
class SpecialViewHolder extends RecyclerView.ViewHolder {
           ...                
   public void bind(int position) {
       ...
       specials.put(position, itemView);
   }
}

Scrap

  • Scrap 是 RecyclerView 在一次布局过程中优先查找 ViewHolder 的临时容器,与 Pool/View Cache 不同。
  • Scrap 仅在布局(预布局/后布局)期间非空;布局结束后内容会被清理或转移到其他缓存/池。
  • 拆分为两类 Scrap: mAttachedScrap (未请求"替换式"变更动画)与 mChangedScrap (请求替换式变更动画,仅用于预布局)。
mAttachedScrap

在"预布局"和"后布局"都可用;表示旧视图可以继续复用,同一个 ViewHolder 在两阶段都能被取回重新附着。

mChangedScrap

只在"预布局"可用;表示需要"替换式"变更动画,预布局保留旧视图用于记录动画起点,后布局应以"新的 ViewHolder "取代旧视图。

相关推荐
_李小白2 小时前
【Android FrameWork】第二十天:AudioTrack
android·gitee
走在路上的菜鸟2 小时前
Android学Dart学习笔记第十节 循环
android·笔记·学习·flutter
LiuYaoheng2 小时前
【Android】RecyclerView 刷新方式全解析:从 notifyDataSetChanged 到 DiffUtil
android·java
春卷同学2 小时前
打砖块 - Electron for 鸿蒙PC项目实战案例
android·electron·harmonyos
wei115562 小时前
compose自定义控件
android
m0_632482502 小时前
Android端测试类型、用例设计、测试工具(不涉及自动化测试)
android
走在路上的菜鸟3 小时前
Android学Dart学习笔记第九节 Patterns
android·笔记·学习·flutter
AllBlue3 小时前
unity导出成安卓工程,集成到安卓显示
android·unity·游戏引擎
QQ_4376643144 小时前
常见题目及答案
android·java·开发语言