Android RecyclerView 缓存机制

RecyclerView 通过多级缓存(Scrap、Cache、ViewCacheExtension 和 RecycledViewPool)来最小化 View 创建和数据绑定的开销。

RecyclerView 的缓存由 Recycler 类管理,它是 RecyclerView 的内部成员。Recycler 使用以下字段存储不同级别的缓存:

1
2
3
4
5
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null; // 用于变更的 Scrap,通常在 DiffUtil 等场景
final ArrayList<ViewHolder> mCachedViews = new ArrayList<>();
ViewCacheExtension mViewCacheExtension; // 开发者自定义扩展
final RecycledViewPool mRecyclerPool = new RecycledViewPool(); // 默认每个 ViewType 缓存 5 个
  • mAttachedScrap:存储仍附加到 RecyclerView 但临时不可见的 ViewHolder(例如动画或布局变更中)。
  • mChangedScrap:类似 mAttachedScrap,但用于数据变更的 ViewHolder(例如使用 AdapterHelper)。
  • mCachedViews:快速缓存,默认大小 2(可通过 setItemViewCacheSize(int) 调整),存储最近移除的 ViewHolder,保留绑定状态。
  • mViewCacheExtension:可选的开发者自定义缓存(通过 setViewCacheExtension() 设置)。
  • mRecyclerPool:回收池,按 ViewType 分组存储重置后的 ViewHolder,默认每个类型 5 个(可通过 setMaxRecycledViews(int viewType, int max) 调整)。

这些字段形成了缓存的层次:Scrap(最高优先级,零开销复用)→ Cache → Extension → Pool → 新创建。

ViewHolder 获取流程(tryGetViewHolderForPositionByDeadline 方法)

当 RecyclerView 需要一个 ViewHolder 时(例如在布局填充中),它调用 Recycler.getViewForPosition(int position),后者委托给 tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)。这个方法是缓存机制的核心,按优先级从缓存中查找 ViewHolder,如果找不到则创建新实例。以下是简化后的源码逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// Recycler.tryGetViewHolderForPositionByDeadline 方法(关键摘录)
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
// ... 参数校验 ...

ViewHolder holder = null;
// 步骤1: 从 Scrap 中查找(最高优先级)
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position); // 从 mChangedScrap 中找
}
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); // 从 mAttachedScrap 或隐藏视图中找
}

if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
final int type = mAdapter.getItemViewType(offsetPosition);

// 步骤2: 从 Cache (mCachedViews) 中查找匹配 position 的 ViewHolder
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// 验证 holder 是否有效
if (!validateViewHolderForOffsetPosition(holder, offsetPosition, dryRun)) {
if (!dryRun) {
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}

if (holder == null && viewCacheExtension != null) {
// 步骤3: 从 ViewCacheExtension 中查找(如果开发者设置了)
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}

if (holder == null) {
// 步骤4: 从 RecycledViewPool 中查找
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal(); // 重置 holder 状态
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}

if (holder == null) {
// 步骤5: 创建新 ViewHolder(最昂贵操作)
long start = getNanoTime();
if (deadlineNs != FOREVER_NS && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
return null; // 超时返回 null
}
holder = mAdapter.createViewHolder(RecyclerView.this, type); // 调用 Adapter.onCreateViewHolder
if (ALLOW_THREAD_GAP_WORK) {
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
}

// ... 后续处理:绑定数据、预布局等 ...
return holder;
}

流程解释: 1. Scrap 优先:先从 mAttachedScrapmChangedScrap 中查找(通过 getChangedScrapViewForPositiongetScrapOrHiddenOrCachedHolderForPosition)。Scrap 中的 ViewHolder 无需重置,直接复用,适合动画或快速滑动场景。 2. Cache 检查:如果 Scrap 为空,从 mCachedViews 中找匹配 ID 或类型的 ViewHolder(getScrapOrCachedViewForId)。如果找到,验证位置有效性;无效则回收。 3. Extension(可选):调用自定义的 ViewCacheExtension.getViewForPositionAndType。 4. Pool 回收:从 mRecyclerPool 获取(getRecycledView(type)),并调用 holder.resetInternal() 重置状态(清除标志、数据等)。 5. 新创建:调用 Adapter.createViewHolder,并记录创建时间以优化未来决策(考虑到 deadline 超时)。

这个方法支持 “dry run” 模式(不实际移除 ViewHolder)和 deadline(纳秒级超时),用于预取或并发优化。

ViewHolder 回收流程(recycleViewHolderInternal 方法)

当 ViewHolder 不再需要时(例如滑动出屏),RecyclerView 调用 recycleViewHolderInternal(ViewHolder holder) 将其放入适当缓存。源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Recycler.recycleViewHolderInternal 方法(关键摘录)
void recycleViewHolderInternal(ViewHolder holder) {
// ... 校验 holder 是否可回收 ...

boolean cached = false;
boolean recycled = false;

if (shouldBeKeptAsScrap(holder) || holder.itemView.getParent() != null) {
// 如果 holder 仍在使用或需要 Scrap,放入 Scrap
if (transientStatePreventsRecycling || holder.isTmpDetached()) {
scrapView(holder.itemView); // 放入 mAttachedScrap
cached = true;
}
} else if (mViewCacheMax <= 0 || forceCache || holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE)) {
// 如果 Cache 已满或 holder 无效,直接回收到 Pool
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
} else {
// 尝试放入 Cache (mCachedViews)
if (mCachedViews.size() >= mViewCacheMax && !mCachedViews.isEmpty()) {
// Cache 满时,移除最旧的并回收到 Pool
recycleCachedViewAt(0);
}
mCachedViews.add(holder); // 添加到 Cache
cached = true;
}

if (!cached && !recycled && holder.isRecyclable()) {
// 默认回收到 Pool
mRecyclerPool.putRecycledView(holder);
recycled = true;
}
}

回收逻辑解释: - Scrap:如果 holder 有临时状态(isTmpDetached())或仍在父视图中,放入 mAttachedScrap(通过 scrapView)。 - Cache:如果 Cache 未满(mCachedViews.size() < mViewCacheMax),直接添加。Cache 满时,移除最早的(recycleCachedViewAt(0))并移到 Pool。 - Pool:调用 mRecyclerPool.putRecycledView(holder),按 ViewType 存储。如果 Pool 满(默认 5),丢弃最旧的。进入 Pool 前,调用 Adapter.onViewRecycled(holder) 释放资源。 - 特殊处理:无效或移除的 holder 直接回收到 Pool,避免污染 Cache。

相关辅助方法: - addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled):重置 holder 并放入 Pool,同时调用 onViewRecycled。 - RecycledViewPool.putRecycledView(ViewHolder scrap):内部使用 SparseArray<ArrayList> 按类型存储。

RecycledViewPool 的实现

RecycledViewPool 是静态可共享的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// RecycledViewPool 类(关键摘录)
public static class RecycledViewPool {
private SparseArray<ScrapData> mScrap = new SparseArray<>();
private int mAttachCountForPoolingContainer = 0;

static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP; // 默认 5
// ... 其他字段如创建时间 ...
}

public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
if (getScrapDataForType(viewType).mMaxScrap <= scrapHeap.size()) {
return; // 满则丢弃
}
scrap.resetInternal();
scrapHeap.add(scrap);
}

public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = getScrapDataForType(viewType);
if (!scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
return scrapHeap.remove(scrapHeap.size() - 1); // LIFO 取出
}
return null;
}
}
  • 按类型管理:使用 SparseArray 存储每个 ViewType 的 ScrapData,每个 ScrapData 有 mScrapHeap (ArrayList) 和 mMaxScrap。
  • 放入/取出:LIFO(后进先出)原则,放入前重置 holder。

性能优化与注意事项

  • 缓存大小调整:setItemViewCacheSize(int size) 修改 mRequestedCacheMax,影响 mCachedViews。
  • 共享 Pool:多个 RecyclerView 可共享同一个 Pool(setRecycledViewPool),减少内存。
  • 预取与超时:tryGetViewHolderForPositionByDeadline 支持 deadlineNs,防止主线程阻塞(结合 GapWorker 预取)。
  • 常见问题:源码中多处校验标志(如 FLAG_INVALID),防止回收无效 holder。开发者需在 onViewRecycled 释放资源,避免泄漏。
  • DiffUtil 集成:数据更新时,使用 mChangedScrap 处理变更 ViewHolder。

总结

RecyclerView 的缓存机制通过 Recycler 的多级结构高效工作:Scrap 用于即时复用,Cache 保留绑定状态,Extension 自定义,Pool 作为最后储备。核心方法tryGetViewHolderForPositionByDeadline 按序查找,recycleViewHolderInternal 智能回收。这种设计使 RecyclerView 在长列表中保持流畅,开发者可通过 API 调整以适应具体场景。