RecyclerView的缓存复用机制

文章目录

  • RecyclerView的缓存复用机制
    • [1. 引入:ListView的问题](#1. 引入:ListView的问题)
      • [1.1 没有复用的后果](#1.1 没有复用的后果)
      • [1.2 RecyclerView的核心思想](#1.2 RecyclerView的核心思想)
    • [2. 四个核心概念](#2. 四个核心概念)
    • [3. ViewHolder的作用](#3. ViewHolder的作用)
      • [3.1 什么是ViewHolder?](#3.1 什么是ViewHolder?)
      • [3.2 为什么要有ViewHolder?](#3.2 为什么要有ViewHolder?)
      • 哪里看出来用了缓存?
    • [4. 四级缓存机制](#4. 四级缓存机制)
      • [4.1 缓存层级](#4.1 缓存层级)
      • [4.2 各层级详解](#4.2 各层级详解)
    • [5. 从列表滑动看缓存流程](#5. 从列表滑动看缓存流程)
      • [5.1 向上滑动时发生了什么?](#5.1 向上滑动时发生了什么?)
      • [5.2 回收过程(Item 0滑出)](#5.2 回收过程(Item 0滑出))
      • [5.3 获取过程(底部需要Item 8)](#5.3 获取过程(底部需要Item 8))
    • [6. 预取机制(Prefetch)](#6. 预取机制(Prefetch))
      • [6.1 什么是预取?](#6.1 什么是预取?)
      • [6.2 优化效果](#6.2 优化效果)
    • [7. 代码示例](#7. 代码示例)
      • [7.1 启用预取(默认开启)](#7.1 启用预取(默认开启))
      • [7.2 设置缓存大小](#7.2 设置缓存大小)
    • [8. 性能对比](#8. 性能对比)
    • [9. 易错点总结](#9. 易错点总结)
    • [10. 一句话总结](#10. 一句话总结)

RecyclerView的缓存复用机制

1. 引入:ListView的问题

1.1 没有复用的后果

假设一个列表有1000条数据,每个Item有一个ImageView加载图片。

如果不做任何优化,滑动列表时会不断创建新的View

java 复制代码
// 没有复用的情况
public View getView(int position, View convertView, ViewGroup parent) {
    // 每次滑动都创建一个新View
    View view = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
    // 绑定数据...
    return view;
}
  • 滑动1000条就创建1000个View
  • 每个View占用内存,GC频繁
  • 滑动卡顿,掉帧严重

1.2 RecyclerView的核心思想

RecyclerView通过四级缓存 + 强制ViewHolder解决了这个问题:

复制代码
核心思想:View不销毁,只重用

2. 四个核心概念

概念 说明 类比
ViewType 每个Item的类型 不同款式的衣服
Adapter 数据 → View的转换器 流水线工人
ViewHolder 持有View的引用,绑定数据 衣架
Recycler 缓存池,管理废弃和复用的View 仓库

3. ViewHolder的作用

3.1 什么是ViewHolder?

ViewHolder是一个持有View引用的容器类,避免每次绑定数据时重复findViewById

java 复制代码
// 标准ViewHolder写法
static class MyViewHolder extends RecyclerView.ViewHolder {
    TextView tvTitle;
    ImageView ivIcon;
    
    public MyViewHolder(View itemView) {
        super(itemView);
        tvTitle = itemView.findViewById(R.id.tv_title);
        ivIcon = itemView.findViewById(R.id.iv_icon);
    }
}

3.2 为什么要有ViewHolder?

操作 传统方式(无ViewHolder) 有ViewHolder
创建Item inflate布局 inflate布局
绑定数据 每次findViewById 直接使用缓存的View引用
性能 每次都要查找控件 只查找一次

不用ViewHolder的话,每次onBindViewHolder都要走一遍findViewById,列表滑动时这个开销就很明显了。

哪里看出来用了缓存?

关键点onBindViewHolder 拿到的是已经创建好的 ViewHolder(里面的 View 已经 inflate 过了)。

onBindViewHolder 被调用时,传入的 holder 参数是 RecyclerView 从缓存里取出来的,它的 itemView 早就存在,tvTitleivIcon 也早就找到了。onBindViewHolder 只负责往这些现成的控件里填新数据。

跟没有缓存的写法对比,一眼就能看出区别:

java

复制代码
// 没有缓存的写法(每次都要重新找控件)
public void bindView(View view, int position) {
    TextView tv = view.findViewById(R.id.tv_title);  // 每次都查一遍
    tv.setText(dataList.get(position).getTitle());
}

// 有缓存的写法(直接复用)
public void onBindViewHolder(MyViewHolder holder, int position) {
    holder.tvTitle.setText(dataList.get(position).getTitle()); // 直接拿,不用查
}

4. 四级缓存机制

4.1 缓存层级

复制代码
                    RecyclerView.Recycler
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
    ┌───▼───┐         ┌─────▼─────┐      ┌──────▼──────┐
    │一级缓存│         │ 二级缓存   │      │  三级缓存    │
    │ mAttached│      │ mCacheViews│     │mViewCacheExt│
    │ Scrap  │         │ (大小2)   │      │  (默认为2)    │
    └────────┘         └───────────┘      └─────────────┘
                                   
    ┌───────────────────────────────────────────┐
    │             四级缓存(RecycledViewPool)   │
    │         按ViewType缓存,默认5个/类型       │
    └───────────────────────────────────────────┘

4.2 各层级详解

层级 名称 大小 存放内容 使用场景
1级 mAttachedScrap 不固定 正在被使用的ViewHolder 布局过程中
2级 mCachedViews 默认为 2 离开屏幕但还保留的View 滑动回收
3级 mViewCacheExtension 开发者自定义 自定义缓存 特殊需求
4级 RecycledViewPool 每类 5 个 按ViewType缓存的View 跨RecyclerView共享

5. 从列表滑动看缓存流程

5.1 向上滑动时发生了什么?

复制代码
屏幕可见区域
┌─────────────────────────┐
│  Item 0 (消失)           │ ← 顶部Item滑出屏幕
├─────────────────────────┤
│  Item 1                  │
│  Item 2                  │
│  Item 3                  │
│  Item 4                  │
├─────────────────────────┤
│  Item 5                  │
│  Item 6                  │
│  Item 7                  │
│  Item 8 (底部新出现)     │ ← 底部需要显示新Item
└─────────────────────────┘

5.2 回收过程(Item 0滑出)

复制代码
Item 0 离开屏幕
    │
    ▼
检查:mCachedViews是否已满?(默认2个)
    │
    ├─ 未满 → 放入mCachedViews(2级缓存)
    │          完整保留View,下次复用无需重新创建
    │
    └─ 已满 → 把mCachedViews中最旧的移到RecycledViewPool(4级缓存)
                 View被回收,重新创建时再inflate

5.3 获取过程(底部需要Item 8)

复制代码
需要显示Item 8
    │
    ▼
1. 从mAttachedScrap查找(1级)
    │   正在使用的View(一般是布局过程中)
    ▼
2. 从mCachedViews查找(2级)
    │   最近滑出屏幕的View,完整保留
    ▼
3. 从mViewCacheExtension查找(3级,一般不用)
    ▼
4. 从RecycledViewPool查找(4级)
    │   按ViewType匹配,需要重新绑定数据
    ▼
5. 以上都没有 → 创建新的ViewHolder并绑定数据

每次查找都要先找1级,1级没有找2级,2级没有找3级,都没有才找4级或重新创建。


6. 预取机制(Prefetch)

6.1 什么是预取?

RecyclerView在滑动时,会提前准备即将出现的Item:

复制代码
滑动方向 →  ↓

当前可见:
[Item 0] [Item 1] [Item 2] [Item 3]

预取任务:
提前准备 Item 4(下一次滑动可能看到)

在用户手指还没滑到那个位置时,系统就已经提前创建好Item放在缓存里了,等到真正要显示的时候直接拿,而不是现场创建。

6.2 优化效果

机制 作用
预取 提前创建/绑定View,减少滑动时的卡顿
异步 在空闲时执行,不影响UI主线程

7. 代码示例

7.1 启用预取(默认开启)

java 复制代码
RecyclerView recyclerView = findViewById(R.id.recyclerView);
// 默认已启用,不需要额外配置

7.2 设置缓存大小

java 复制代码
// 设置mCachedViews大小(默认2)
recyclerView.setItemViewCacheSize(10);

// 设置RecycledViewPool大小
RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool();
pool.setMaxRecycledViews(viewType, 10);
recyclerView.setRecycledViewPool(pool);

8. 性能对比

场景 无复用 有复用
滑动1000条创建View次数 1000次 约屏幕可见数+缓存数
每次绑定数据findViewById 每次都要 只创建时一次
内存占用 越来越高 稳定
GC频率

9. 易错点总结

易错点 说明
不同ViewType的View不能复用 不同布局无法互相复用
RecycledViewPool不区分ViewType的,只按viewType区分 不同类型的View独立缓存
setItemViewCacheSize太大 缓存太多会导致内存浪费
忘记调用adapter.notifyDataSetChanged 数据变化后界面不更新

10. 一句话总结

RecyclerView的缓存复用机制就是四级缓存依次查找:

  1. mAttachedScrap(当前正在用的)
  2. mCachedViews(刚滑出去的,完整保留)
  3. mViewCacheExtension(开发者自定义)
  4. RecycledViewPool(按类型缓存,需要重新创建)

能复用就不创建,减少频繁布局和内存分配,让滑动更流畅。

相关推荐
Escalating_xu2 小时前
【Linux mmap 文件映射】从虚拟地址到页缓存:读写文件、匿名映射与极简 malloc
linux·缓存
西瓜拿铁好喝5 小时前
2026 语义缓存实战:把命中契约写进SPEC,MonkeyCode 云端跑通
人工智能·机器学习·缓存
小刘在重生~16 小时前
Java 常用类|包装类 装箱拆箱、Integer 缓存面试题
java·python·缓存
极小狐1 天前
CI 流水线提速实战:缓存设计与依赖代理的工程实践
缓存·ci/cd·gitlab·devops
莫得感情 o1 天前
Redis 09 · 高可用:Sentinel 如何发现故障并完成切主
redis·缓存·sentinel
BUG研究员_1 天前
LangGraph节点缓存-CachePolicy
python·缓存·langraph
tryxr1 天前
Redis 的背景知识
数据库·redis·缓存
莫得感情 o1 天前
Redis 08 · 主从复制:全量同步、增量同步与复制延迟
redis·缓存
刃神太酷啦2 天前
Redis 进阶核心:持久化 (RDB/AOF)、事务与主从复制全解析----《Hello Redis!》(5)
linux·c语言·数据库·c++·redis·缓存·bootstrap