此篇是浅谈,后续会发布相关源码分析
RecyclerView 是一种在 Android 开发中用于高效展示大量数据列表的视图容器,(ios类似的组件:UICollectionView)
缓存的使用过程
当LayoutManager请求RecyclerView提供某个位置的View时,RecyclerView会按以下顺序查找:
- 已变更的临时缓存(Changed Scrap) (用于处理数据变更时的动画,如
notifyItemChanged()) - 已附加的临时缓存(Attached Scrap) (屏幕内尚未完全分离的ViewHolder)
- 未移除的隐藏视图 (通过
mChildHelper查找隐藏但未移除的View) - 视图缓存(View Cache) (屏幕外已滑出的ViewHolder,默认缓存2个)
- 通过稳定ID二次搜索 (若适配器支持稳定ID,会按ID在缓存中精确匹配)
- 自定义缓存扩展(ViewCacheExtension) (开发者可自定义缓存策略,但极少使用)
- 全局回收池(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
应用场景
- 滚动过程中,视图超出了 RecyclerView 的边界。
如果 mCachedViews 还有空位会优先放入其中,然后才放入 RecycledViewPool
- 数据已更改,因此视图不再可见。当消失动画结束后,才会向池中添加新元素。
比如:某 item 被删除或位置变化,Recycler 启动 item disappear animation(消失动画), 回收时再遵循本地缓存优先、缓存满则入全局池的规则。。
- 视图缓存中的某个项目已被更新或删除。
- 在查找 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 能减少闪烁和错位,
适合作为"唯一身份",不能随意变化
- 布局管理器在布局前添加了一个视图,但在布局后没有添加该视图。比如一些 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 "取代旧视图。