Android开发进阶:RecyclerView的四级缓存机制详解
Android开发进阶:RecyclerView的四级缓存机制详解
RecyclerView的缓存机制是Android开发中一个非常重要但又容易被忽视的环节。良好的缓存策略不仅能提升应用的性能,还能优化用户体验。本文将从源码层面深入解析RecyclerView的四级缓存系统:Scrap、CachedViews、ViewCacheExtension和RecycledViewPool,帮助开发者更好地理解其工作原理和使用场景。
一.Scrap
Scrap是RecyclerView的第一级缓存,包括mAttachedScrap和mChangedScrap。这两个缓存主要在界面重新绘制时发挥作用,与滚动操作无关。
1.Scrap的缓存过程
缓存过程主要发生在LinearLayoutManager的scrapOrRecycleView
方法中:
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
缓存条件主要由以下三个判断决定:
viewHolder.isInvalid()
:判断ViewHolder是否为无效状态viewHolder.isRemoved()
:判断ViewHolder是否被移除mRecyclerView.mAdapter.hasStableIds()
:判断适配器是否使用稳定ID
通过调试可以得出以下结论:
- 正常数据设置(如第一次设置数据或滚动时)不会触发mAttachedScrap和mChangedScrap的缓存
- 调用适配器的
notifyItemRemoved
方法会触发缓存 - 设置适配器的
stableId
为true也会触发缓存
具体缓存到哪个容器(mAttachedScrap或mChangedScrap)由以下条件决定:
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
2.Scrap的复用
Scrap的复用过程主要发生在tryGetViewHolderForPositionByDeadline
方法中:
需要注意的是,dispatchLayoutStep3
方法中会清空Scrap缓存:
void removeAndRecycleScrapInt(Recycler recycler) {
final int scrapCount = recycler.getScrapCount();
for (int i = scrapCount - 1; i >= 0; i--) {
final View scrap = recycler.getScrapViewAt(i);
final ViewHolder vh = getChildViewHolderInt(scrap);
if (vh.shouldIgnore()) {
continue;
}
vh.setIsRecyclable(false);
if (vh.isTmpDetached()) {
mRecyclerView.removeDetachedView(scrap, false);
}
if (mRecyclerView.mItemAnimator != null) {
mRecyclerView.mItemAnimator.endAnimation(vh);
}
vh.setIsRecyclable(true);
recycler.quickRecycleScrapView(scrap);
}
recycler.clearScrap();
if (scrapCount > 0) {
mRecyclerView.invalidate();
}
}
Scrap缓存的主要作用是应对轻量级刷新,如移除或更新单个条目,避免不必要的数据重设。
二、CachedViews
CachedViews是与滚动相关的缓存,主要用于存储被移出屏幕的View,容量默认为2。其复用过程主要发生在getScrapOrHiddenOrCachedHolderForPosition
方法中:
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
if (!holder.isInvalid() && holder.getLayoutPosition() == position
&& !holder.isAttachedToTransitionOverlay()) {
if (!dryRun) {
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
return holder;
}
}
CachedViews主要用于处理来回滑动的情况,避免不必要的数据重设。
三、ViewCacheExtension
ViewCacheExtension是一个可扩展的缓存帮助类,开发者可以根据需要实现自定义的缓存逻辑。其主要作用是在Scrap和CachedViews之外提供额外的缓存层。
四、RecycledViewPool
RecycledViewPool是RecyclerView的终极回收站,以SparseArray嵌套ArrayList的形式保存ViewHolder,按itemType区分缓存。默认缓存大小为5,可以设置最大缓存数量。
缓存池定义了SparseArray mScrap,它是一个根据不同itemType来保存静态类ScrapData对象的SparseArray,ScrapData中包含了ArrayList mScrapHeap ,mScrapHeap是保存该itemType类型下ViewHolder的ArrayList。
RecycledViewPool的主要特点是会保存ViewHolder对象但不保留数据信息,在复用时需要重新调用onBindViewHolder
方法绑定数据。