深度探秘!Android RecyclerView 缓存机制的底层原理全解析
一、RecyclerView 缓存机制概述
1.1 缓存机制的重要性
在Android应用开发中,RecyclerView作为展示大量数据的核心组件,其性能优化至关重要。而缓存机制是提升RecyclerView性能的关键所在。通过缓存复用视图,避免了频繁的视图创建与销毁操作,显著减少了CPU和内存资源的消耗,从而提升了列表滚动的流畅性,为用户带来更顺滑的使用体验。例如,在新闻资讯类应用中,大量的新闻条目展示如果没有高效的缓存机制,会导致滚动卡顿,影响用户浏览新闻的体验。
1.2 缓存机制的核心目标
RecyclerView的缓存机制主要有两个核心目标:一是减少视图的创建和销毁次数,二是快速获取可用视图用于展示。通过缓存已创建的视图,在需要显示新的列表项时,优先从缓存中获取,而不是重新创建新视图,从而大大提高了数据展示的效率。
1.3 缓存机制的基本组成
RecyclerView的缓存机制主要由四级缓存构成,分别是mAttachedScrap 、mCachedViews 、mViewCacheExtension 和mRecyclerPool。每一级缓存都有其独特的功能和适用场景,它们相互协作,共同实现高效的视图缓存与复用。
二、RecyclerView 四级缓存详解
2.1 第一级缓存:mAttachedScrap
2.1.1 mAttachedScrap的作用
mAttachedScrap
是RecyclerView的第一级缓存,它存储的是当前屏幕上已经附着的视图。当RecyclerView进行局部刷新或者布局变化时,这些视图可以直接复用,无需重新创建和绑定数据,从而实现快速更新。
2.1.2 mAttachedScrap的相关源码分析
在RecyclerView的源码中,mAttachedScrap
是一个ArrayList<View>
类型的变量,用于存储视图。
java
// RecyclerView类中定义mAttachedScrap
ArrayList<View> mAttachedScrap = new ArrayList<>();
当RecyclerView进行布局更新时,会调用detachAndScrapAttachedViews
方法将当前屏幕上的视图分离并添加到mAttachedScrap
中。
java
/**
* 将当前屏幕上附着的视图分离并添加到mAttachedScrap缓存中
* @param recycler RecyclerView的Recycler对象,用于视图的获取和回收
*/
void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
// 将视图从RecyclerView中分离
detachViewAt(i);
// 将分离的视图添加到mAttachedScrap缓存中
recycler.scrapView(v);
}
}
在Recycler
类中,scrapView
方法将视图添加到mAttachedScrap
中。
java
// Recycler类中的scrapView方法
void scrapView(@NonNull View view) {
final ViewHolder holder = getChildViewHolderInt(view);
// 判断ViewHolder的状态,如果是已附着状态,则添加到mAttachedScrap中
if (holder.isAttached()) {
if (mAttachedScrap == null) {
mAttachedScrap = new ArrayList<>();
}
mAttachedScrap.add(view);
holder.unScrap();
}
}
当需要复用这些视图时,会从mAttachedScrap
中获取。例如在fill
方法中(不同的LayoutManager都有类似逻辑,以LinearLayoutManager为例):
java
// LinearLayoutManager类中的fill方法
int fill(@NonNull RecyclerView.Recycler recycler, @NonNull LayoutState layoutState,
@NonNull RecyclerView.State state, boolean stopOnFocusable) {
// 省略其他代码...
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
layoutChunkResult.resetInternal();
// 优先从mAttachedScrap中获取视图
View view = layoutState.next(recycler);
// 省略视图布局相关代码...
}
// 省略其他代码...
return start - layoutState.mAvailable;
}
在layoutState.next
方法中,会尝试从mAttachedScrap
中获取视图:
java
// LayoutState类中的next方法
View next(@NonNull RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
// 如果mAttachedScrap中没有可用视图,则从其他缓存获取
final View view = recycler.getViewForPosition(mCurrentPosition);
return view;
}
2.2 第二级缓存:mCachedViews
2.2.1 mCachedViews的作用
mCachedViews
是RecyclerView的第二级缓存,它存储的是最近被移除屏幕的视图。这些视图已经绑定过数据,当它们再次进入屏幕时,可以直接复用,无需重新绑定数据,进一步提高了视图复用的效率。
2.2.2 mCachedViews的相关源码分析
在RecyclerView的Recycler
类中,mCachedViews
是一个ArrayList<ViewHolder>
类型的变量。
java
// Recycler类中定义mCachedViews
ArrayList<ViewHolder> mCachedViews = new ArrayList<>();
// 缓存的默认最大容量
private static final int DEFAULT_CACHE_SIZE = 2;
当视图从屏幕上移除时,会被添加到mCachedViews
中。在Recycler
类的recycleViewHolderInternal
方法中可以看到相关逻辑:
java
// Recycler类中的recycleViewHolderInternal方法
void recycleViewHolderInternal(@NonNull ViewHolder holder) {
// 判断ViewHolder是否可以被回收
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// 如果缓存数量未达到上限,则将ViewHolder添加到mCachedViews中
if (mCachedViews.size() < mViewCacheMax
&& holder.getScrapContainer() == null) {
int cachedViewSize = mCachedViews.size();
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK) {
// 如果允许线程间隙工作,则根据位置计算目标缓存索引
for (int i = 0; i < cachedViewSize; i++) {
if (mRecyclerPool.willCreateInNextScrap(mCachedViews.get(i))) {
targetCacheIndex = i;
break;
}
}
}
// 将ViewHolder添加到mCachedViews中
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
}
}
if (!cached) {
// 如果未添加到mCachedViews中,则将ViewHolder添加到mRecyclerPool中
addViewHolderToRecycledViewPool(holder, true);
}
}
当需要获取视图时,会优先从mCachedViews
中查找。在Recycler
类的getViewForPosition
方法中:
java
// Recycler类中的getViewForPosition方法
@NonNull View getViewForPosition(int position) {
return getViewForPosition(position, false /* dryRun */);
}
// 重载的getViewForPosition方法
@NonNull View getViewForPosition(int position, boolean dryRun) {
// 尝试从mAttachedScrap中获取视图
final ViewHolder holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
return holder.itemView;
}
// 省略从其他缓存获取视图的代码...
}
在getScrapOrHiddenOrCachedHolderForPosition
方法中,会从mCachedViews
中查找可用的ViewHolder:
java
// Recycler类中的getScrapOrHiddenOrCachedHolderForPosition方法
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
// 尝试从mAttachedScrap中获取ViewHolder
final ViewHolder scrap = getScrapViewForPosition(position);
if (scrap != null) {
return scrap;
}
// 从mCachedViews中查找ViewHolder
final int cachedViewSize = mCachedViews.size();
for (int i = 0; i < cachedViewSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
if (!holder.wasReturnedFromScrap()
&& holder.getLayoutPosition() == position) {
if (!dryRun) {
// 将找到的ViewHolder移动到缓存列表头部
mCachedViews.remove(i);
mCachedViews.add(0, holder);
}
return holder;
}
}
// 省略从其他缓存获取ViewHolder的代码...
}
2.3 第三级缓存:mViewCacheExtension
2.3.1 mViewCacheExtension的作用
mViewCacheExtension
是RecyclerView提供的一个扩展缓存接口,开发者可以实现该接口来自定义缓存逻辑,以满足特定的业务需求。它可以在mAttachedScrap
和mCachedViews
无法满足视图复用需求时发挥作用。
2.3.2 mViewCacheExtension的相关源码分析
在RecyclerView中,mViewCacheExtension
是一个ViewCacheExtension
类型的变量。
java
// RecyclerView类中定义mViewCacheExtension
ViewCacheExtension mViewCacheExtension;
ViewCacheExtension
是一个抽象接口,定义如下:
java
// ViewCacheExtension接口
public abstract static class ViewCacheExtension {
/**
* 尝试从扩展缓存中获取ViewHolder
* @param recycler RecyclerView的Recycler对象
* @param position 列表项的位置
* @param type 列表项的类型
* @return 找到的ViewHolder,若未找到则返回null
*/
@Nullable
public ViewHolder getViewForPositionAndType(@NonNull Recycler recycler, int position, int type) {
return null;
}
}
在Recycler
类的getViewForPosition
方法中,当从mAttachedScrap
和mCachedViews
中都无法获取到视图时,会尝试从mViewCacheExtension
中获取:
java
// Recycler类中的getViewForPosition方法
@NonNull View getViewForPosition(int position) {
return getViewForPosition(position, false /* dryRun */);
}
// 重载的getViewForPosition方法
@NonNull View getViewForPosition(int position, boolean dryRun) {
// 尝试从mAttachedScrap中获取视图
final ViewHolder holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
return holder.itemView;
}
final int type = mAdapter.getItemViewType(position);
// 尝试从mViewCacheExtension中获取视图
View view = tryGetViewHolderForPositionByDeadline(position, dryRun, mRecyclerPool, type,
DEFAULT_MAX_IGNORE_DURATION_NS);
if (view != null) {
return view;
}
// 省略从其他缓存获取视图的代码...
}
在tryGetViewHolderForPositionByDeadline
方法中,调用mViewCacheExtension
的getViewForPositionAndType
方法获取视图:
java
// Recycler类中的tryGetViewHolderForPositionByDeadline方法
@Nullable
View tryGetViewHolderForPositionByDeadline(int position, boolean dryRun,
@NonNull RecycledViewPool recycledViewPool, int type,
long deadlineNs) {
// 省略其他代码...
if (mViewCacheExtension != null) {
// 调用mViewCacheExtension的getViewForPositionAndType方法
final ViewHolder view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
if (!dryRun) {
// 如果不是预运行,则将ViewHolder添加到mAttachedScrap中
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
scrapView(view);
}
return view.itemView;
}
}
// 省略其他代码...
}
2.4 第四级缓存:mRecyclerPool
2.4.1 mRecyclerPool的作用
mRecyclerPool
是RecyclerView的第四级缓存,也是最后一级缓存。它存储的是被回收的ViewHolder,并且按照视图类型进行分类。当所有上级缓存都无法提供可用视图时,会从mRecyclerPool
中获取,并重新绑定数据后使用。
2.4.2 mRecyclerPool的相关源码分析
在RecyclerView的Recycler
类中,mRecyclerPool
是一个RecycledViewPool
类型的变量。
java
// Recycler类中定义mRecyclerPool
RecycledViewPool mRecyclerPool;
RecycledViewPool
类中,使用一个SparseArray
来存储不同类型的ViewHolder列表,SparseArray
的键为视图类型,值为对应的ViewHolder列表。
java
// RecycledViewPool类
public class RecycledViewPool {
// 存储不同类型ViewHolder列表的SparseArray
private final SparseArray<ArrayList<ViewHolder>> mScrap = new SparseArray<>();
// 每个类型默认的缓存容量
private static final int DEFAULT_MAX_SCRAP = 5;
/**
* 将ViewHolder添加到mRecyclerPool中
* @param viewHolder 要添加的ViewHolder
* @param force 强制添加标志
*/
void addViewHolderToRecycledViewPool(@NonNull ViewHolder viewHolder, boolean force) {
final int viewType = viewHolder.getItemViewType();
// 获取对应视图类型的ViewHolder列表
ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType);
if (mScrap.get(viewType) == null) {
// 如果列表不存在,则创建一个新的列表
scrapHeap = new ArrayList<>();
mScrap.put(viewType, scrapHeap);
}
if (force || scrapHeap.size() < DEFAULT_MAX_SCRAP) {
// 如果达到强制添加条件或列表未满,则将ViewHolder添加到列表中
scrapHeap.add(viewHolder);
viewHolder.resetInternal();
}
}
/**
* 从mRecyclerPool中获取ViewHolder
* @param viewType 视图类型
* @return 获取到的ViewHolder,若未找到则返回null
*/
@Nullable
ViewHolder getRecycledView(int viewType) {
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType);
if (scrapHeap != null && scrapHeap.size() > 0) {
// 从列表中取出一个ViewHolder
final int last = scrapHeap.size() - 1;
ViewHolder holder = scrapHeap.get(last);
scrapHeap.remove(last);
return holder;
}
return null;
}
/**
* 获取对应视图类型的ViewHolder列表
* @param viewType 视图类型
* @return 对应的ViewHolder列表
*/
private ArrayList<ViewHolder> getScrapDataForType(int viewType) {
return mScrap.get(viewType);
}
}
在Recycler
类的getViewForPosition
方法中,当从其他缓存都无法获取到视图时,会从mRecyclerPool
中获取:
java
// Recycler类中的getViewForPosition方法
@NonNull View getViewForPosition(int position) {
return getViewForPosition(position, false /* dryRun */);
}
// 重载的getViewForPosition方法
@NonNull View getViewForPosition(int position, boolean dryRun) {
// 尝试从mAttachedScrap中获取视图
final ViewHolder holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
return holder.itemView;
}
final int type = mAdapter.getItemViewType(position);
// 尝试从mViewCacheExtension中获取视图
View view = tryGetViewHolderForPositionByDeadline(position, dryRun, mRecyclerPool, type,
DEFAULT_MAX_IGNORE_DURATION_NS);
if (view != null) {
return view;
}
// 从mRecyclerPool中获取ViewHolder
ViewHolder scrap = mRecyclerPool.getRecycledView(type);
if (scrap != null) {
// 重新绑定数据
scrap.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayList(scrap);
}
onViewRecycled(scrap);
if (DEBUG) {
Log.d(TAG, "getViewForPosition: retrieved scrap " + scrap);
}
return scrap.itemView;
}
// 省略创建新视图的代码...
}
三、RecyclerView 缓存的工作流程
3.1 视图回收流程
当 RecyclerView 中的视图不再显示在屏幕上时,就会触发视图回收流程。这个过程主要涉及将视图从屏幕上移除,并根据视图的状态和缓存规则将其添加到相应的缓存中。以下是详细的源码分析和流程说明。
3.1.1 触发视图回收的时机
视图回收通常在滚动操作、数据更新或布局变化等情况下触发。以滚动操作为例,当用户滚动 RecyclerView 时,一些视图会移出屏幕范围,此时就需要对这些视图进行回收。在 RecyclerView
的 onTouchEvent
方法中处理滚动事件,当滚动距离达到一定阈值时,会调用 scrollBy
或 scrollToPosition
等方法,进而触发视图回收逻辑。
java
// RecyclerView类的onTouchEvent方法
@Override
public boolean onTouchEvent(MotionEvent e) {
// 处理触摸事件
switch (action) {
case MotionEvent.ACTION_MOVE: {
// 计算滚动距离
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (mScrollState != SCROLL_STATE_DRAGGING) {
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
if (dx > 0) {
dx -= mTouchSlop;
} else {
dx += mTouchSlop;
}
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
if (mScrollState == SCROLL_STATE_DRAGGING) {
// 调用scrollBy方法进行滚动
scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev);
}
}
}
break;
}
return true;
}
3.1.2 视图回收的具体实现
在 RecyclerView
的 Recycler
类中,recycleViewHolderInternal
方法负责将视图回收并添加到相应的缓存中。该方法会根据视图的状态和缓存规则,决定将视图添加到 mCachedViews
还是 mRecyclerPool
中。
java
// Recycler类的recycleViewHolderInternal方法
void recycleViewHolderInternal(@NonNull ViewHolder holder) {
// 判断ViewHolder是否可以被回收
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// 如果缓存数量未达到上限,则将ViewHolder添加到mCachedViews中
if (mCachedViews.size() < mViewCacheMax
&& holder.getScrapContainer() == null) {
int cachedViewSize = mCachedViews.size();
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK) {
// 如果允许线程间隙工作,则根据位置计算目标缓存索引
for (int i = 0; i < cachedViewSize; i++) {
if (mRecyclerPool.willCreateInNextScrap(mCachedViews.get(i))) {
targetCacheIndex = i;
break;
}
}
}
// 将ViewHolder添加到mCachedViews中
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
}
}
if (!cached) {
// 如果未添加到mCachedViews中,则将ViewHolder添加到mRecyclerPool中
addViewHolderToRecycledViewPool(holder, true);
}
}
3.1.3 视图添加到 mCachedViews 的条件
要将视图添加到 mCachedViews
中,需要满足以下条件:
mViewCacheMax
大于 0,即缓存容量不为 0。ViewHolder
不包含FLAG_INVALID
、FLAG_REMOVED
、FLAG_UPDATE
或FLAG_ADAPTER_POSITION_UNKNOWN
这些标志。mCachedViews
的数量未达到上限。ViewHolder
没有被其他容器持有。
3.1.4 视图添加到 mRecyclerPool 的条件
如果视图不满足添加到 mCachedViews
的条件,则会被添加到 mRecyclerPool
中。在 RecycledViewPool
类的 addViewHolderToRecycledViewPool
方法中,会根据视图类型将其添加到相应的列表中。
java
// RecycledViewPool类的addViewHolderToRecycledViewPool方法
void addViewHolderToRecycledViewPool(@NonNull ViewHolder viewHolder, boolean force) {
final int viewType = viewHolder.getItemViewType();
// 获取对应视图类型的ViewHolder列表
ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType);
if (mScrap.get(viewType) == null) {
// 如果列表不存在,则创建一个新的列表
scrapHeap = new ArrayList<>();
mScrap.put(viewType, scrapHeap);
}
if (force || scrapHeap.size() < DEFAULT_MAX_SCRAP) {
// 如果达到强制添加条件或列表未满,则将ViewHolder添加到列表中
scrapHeap.add(viewHolder);
viewHolder.resetInternal();
}
}
3.2 视图复用流程
当需要显示新的列表项时,RecyclerView 会尝试从缓存中获取可用的视图进行复用。视图复用流程会依次从各级缓存中查找视图,直到找到合适的视图或创建一个新的视图。以下是详细的源码分析和流程说明。
3.2.1 视图复用的触发时机
视图复用通常在滚动操作、数据更新或布局变化等情况下触发。当需要显示新的列表项时,会调用 RecyclerView
的 getViewForPosition
方法来获取视图。
java
// RecyclerView类的getViewForPosition方法
@NonNull View getViewForPosition(int position) {
return getViewForPosition(position, false /* dryRun */);
}
// 重载的getViewForPosition方法
@NonNull View getViewForPosition(int position, boolean dryRun) {
// 尝试从mAttachedScrap中获取视图
final ViewHolder holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
return holder.itemView;
}
final int type = mAdapter.getItemViewType(position);
// 尝试从mViewCacheExtension中获取视图
View view = tryGetViewHolderForPositionByDeadline(position, dryRun, mRecyclerPool, type,
DEFAULT_MAX_IGNORE_DURATION_NS);
if (view != null) {
return view;
}
// 从mRecyclerPool中获取ViewHolder
ViewHolder scrap = mRecyclerPool.getRecycledView(type);
if (scrap != null) {
// 重新绑定数据
scrap.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayList(scrap);
}
onViewRecycled(scrap);
if (DEBUG) {
Log.d(TAG, "getViewForPosition: retrieved scrap " + scrap);
}
return scrap.itemView;
}
// 创建新的视图
ViewHolder holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (DEBUG) {
Log.d(TAG, "getViewForPosition: created new ViewHolder " + holder);
}
return holder.itemView;
}
3.2.2 从 mAttachedScrap 中获取视图
在 getScrapOrHiddenOrCachedHolderForPosition
方法中,会首先尝试从 mAttachedScrap
中获取视图。
java
// Recycler类的getScrapOrHiddenOrCachedHolderForPosition方法
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
// 尝试从mAttachedScrap中获取ViewHolder
final ViewHolder scrap = getScrapViewForPosition(position);
if (scrap != null) {
return scrap;
}
// 从mCachedViews中查找ViewHolder
final int cachedViewSize = mCachedViews.size();
for (int i = 0; i < cachedViewSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
if (!holder.wasReturnedFromScrap()
&& holder.getLayoutPosition() == position) {
if (!dryRun) {
// 将找到的ViewHolder移动到缓存列表头部
mCachedViews.remove(i);
mCachedViews.add(0, holder);
}
return holder;
}
}
// 省略从其他缓存获取ViewHolder的代码...
}
3.2.3 从 mCachedViews 中获取视图
如果从 mAttachedScrap
中未获取到视图,则会从 mCachedViews
中查找。在 getScrapOrHiddenOrCachedHolderForPosition
方法中,会遍历 mCachedViews
列表,找到与当前位置匹配的 ViewHolder
。
java
// Recycler类的getScrapOrHiddenOrCachedHolderForPosition方法
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
// 尝试从mAttachedScrap中获取ViewHolder
final ViewHolder scrap = getScrapViewForPosition(position);
if (scrap != null) {
return scrap;
}
// 从mCachedViews中查找ViewHolder
final int cachedViewSize = mCachedViews.size();
for (int i = 0; i < cachedViewSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
if (!holder.wasReturnedFromScrap()
&& holder.getLayoutPosition() == position) {
if (!dryRun) {
// 将找到的ViewHolder移动到缓存列表头部
mCachedViews.remove(i);
mCachedViews.add(0, holder);
}
return holder;
}
}
// 省略从其他缓存获取ViewHolder的代码...
}
3.2.4 从 mViewCacheExtension 中获取视图
如果从 mAttachedScrap
和 mCachedViews
中都未获取到视图,则会尝试从 mViewCacheExtension
中获取。在 tryGetViewHolderForPositionByDeadline
方法中,会调用 mViewCacheExtension
的 getViewForPositionAndType
方法。
java
// Recycler类的tryGetViewHolderForPositionByDeadline方法
@Nullable
View tryGetViewHolderForPositionByDeadline(int position, boolean dryRun,
@NonNull RecycledViewPool recycledViewPool, int type,
long deadlineNs) {
// 省略其他代码...
if (mViewCacheExtension != null) {
// 调用mViewCacheExtension的getViewForPositionAndType方法
final ViewHolder view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
if (!dryRun) {
// 如果不是预运行,则将ViewHolder添加到mAttachedScrap中
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
scrapView(view);
}
return view.itemView;
}
}
// 省略其他代码...
}
3.2.5 从 mRecyclerPool 中获取视图
如果从 mAttachedScrap
、mCachedViews
和 mViewCacheExtension
中都未获取到视图,则会从 mRecyclerPool
中获取。在 Recycler
类的 getViewForPosition
方法中,会调用 mRecyclerPool
的 getRecycledView
方法。
java
// Recycler类的getViewForPosition方法
@NonNull View getViewForPosition(int position) {
return getViewForPosition(position, false /* dryRun */);
}
// 重载的getViewForPosition方法
@NonNull View getViewForPosition(int position, boolean dryRun) {
// 尝试从mAttachedScrap中获取视图
final ViewHolder holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
return holder.itemView;
}
final int type = mAdapter.getItemViewType(position);
// 尝试从mViewCacheExtension中获取视图
View view = tryGetViewHolderForPositionByDeadline(position, dryRun, mRecyclerPool, type,
DEFAULT_MAX_IGNORE_DURATION_NS);
if (view != null) {
return view;
}
// 从mRecyclerPool中获取ViewHolder
ViewHolder scrap = mRecyclerPool.getRecycledView(type);
if (scrap != null) {
// 重新绑定数据
scrap.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayList(scrap);
}
onViewRecycled(scrap);
if (DEBUG) {
Log.d(TAG, "getViewForPosition: retrieved scrap " + scrap);
}
return scrap.itemView;
}
// 创建新的视图
ViewHolder holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (DEBUG) {
Log.d(TAG, "getViewForPosition: created new ViewHolder " + holder);
}
return holder.itemView;
}
3.2.6 创建新的视图
如果从各级缓存中都未获取到视图,则会调用 RecyclerView.Adapter
的 createViewHolder
方法创建一个新的视图。
java
// RecyclerView.Adapter的createViewHolder方法
@NonNull
public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);
3.3 数据更新时的缓存处理
当 RecyclerView 的数据发生更新时,需要对缓存进行相应的处理,以确保显示的内容与数据一致。以下是不同数据更新操作时的缓存处理分析。
3.3.1 插入数据
当调用 RecyclerView.Adapter
的 notifyItemInserted
方法插入数据时,RecyclerView 会根据插入位置和缓存情况进行处理。在 RecyclerView
的 dispatchLayoutStep2
方法中,会处理数据更新事件。
java
// RecyclerView的dispatchLayoutStep2方法
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.mInPreLayout = false;
// 进行布局计算
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// 完成布局
mState.mLayoutStep = State.STEP_ANIMATIONS;
stopInterceptRequestLayout(false);
}
在 LayoutManager
的 onLayoutChildren
方法中,会根据插入位置和缓存情况重新布局视图。
java
// LayoutManager的onLayoutChildren方法
public abstract void onLayoutChildren(@NonNull Recycler recycler, @NonNull State state);
3.3.2 删除数据
当调用 RecyclerView.Adapter
的 notifyItemRemoved
方法删除数据时,RecyclerView 会将被删除位置的视图从屏幕上移除,并将其添加到相应的缓存中。在 RecyclerView
的 dispatchLayoutStep2
方法中,会处理数据更新事件。
java
// RecyclerView的dispatchLayoutStep2方法
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.mInPreLayout = false;
// 进行布局计算
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// 完成布局
mState.mLayoutStep = State.STEP_ANIMATIONS;
stopInterceptRequestLayout(false);
}
在 LayoutManager
的 onLayoutChildren
方法中,会根据删除位置和缓存情况重新布局视图。
java
// LayoutManager的onLayoutChildren方法
public abstract void onLayoutChildren(@NonNull Recycler recycler, @NonNull State state);
3.3.3 更新数据
当调用 RecyclerView.Adapter
的 notifyItemChanged
方法更新数据时,RecyclerView 会尝试从缓存中获取对应的视图,并更新其数据。如果缓存中没有对应的视图,则会重新创建一个视图并绑定新的数据。在 RecyclerView
的 dispatchLayoutStep2
方法中,会处理数据更新事件。
java
// RecyclerView的dispatchLayoutStep2方法
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.mInPreLayout = false;
// 进行布局计算
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// 完成布局
mState.mLayoutStep = State.STEP_ANIMATIONS;
stopInterceptRequestLayout(false);
}
在 LayoutManager
的 onLayoutChildren
方法中,会根据更新位置和缓存情况重新布局视图。
java
// LayoutManager的onLayoutChildren方法
public abstract void onLayoutChildren(@NonNull Recycler recycler, @NonNull State state);
四、RecyclerView 缓存机制的性能优化
4.1 合理设置缓存大小
mCachedViews
和 mRecyclerPool
的缓存大小会影响 RecyclerView 的性能。合理设置缓存大小可以在保证视图复用效率的同时,避免过多的内存占用。
4.1.1 mCachedViews 缓存大小设置
mCachedViews
的默认缓存大小为 2。如果列表项的复用频率较高,可以适当增加 mCachedViews
的大小,以提高视图复用的效率。可以通过以下方式设置 mCachedViews
的大小:
java
// 获取RecyclerView的Recycler对象
RecyclerView.Recycler recycler = recyclerView.getRecycler();
// 设置mCachedViews的大小
recycler.setViewCacheSize(5);
4.1.2 mRecyclerPool 缓存大小设置
mRecyclerPool
的默认缓存大小为每个类型 5 个。如果列表项的类型较多或复用频率较高,可以适当增加 mRecyclerPool
的大小。可以通过以下方式设置 mRecyclerPool
的大小:
java
// 创建RecycledViewPool对象
RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();
// 设置每个类型的缓存大小
recycledViewPool.setMaxRecycledViews(viewType, 10);
// 将RecycledViewPool设置给RecyclerView
recyclerView.setRecycledViewPool(recycledViewPool);
4.2 优化视图创建和绑定逻辑
视图的创建和绑定是比较耗时的操作,优化这部分逻辑可以提高 RecyclerView 的性能。
4.2.1 减少视图创建开销
尽量复用视图,避免频繁创建新的视图。可以通过合理设置缓存大小和优化视图类型来减少视图创建的开销。
4.2.2 优化数据绑定逻辑
在 RecyclerView.Adapter
的 onBindViewHolder
方法中,尽量减少不必要的操作,如避免在该方法中进行耗时的计算或网络请求。可以将这些操作提前处理,只在 onBindViewHolder
方法中进行简单的数据绑定。
java
// RecyclerView.Adapter的onBindViewHolder方法
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
// 获取数据
Data data = dataList.get(position);
// 简单的数据绑定
holder.textView.setText(data.getText());
}
4.3 利用预取机制
RecyclerView 提供了预取机制,可以在滚动过程中提前加载下一个或多个视图,以提高滚动的流畅性。可以通过以下方式启用预取机制:
java
// 创建LinearLayoutManager对象
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
// 启用预取机制
layoutManager.setItemPrefetchEnabled(true);
// 设置预取的数量
layoutManager.setInitialPrefetchItemCount(2);
// 将LayoutManager设置给RecyclerView
recyclerView.setLayoutManager(layoutManager);
4.4 避免不必要的刷新
在数据更新时,尽量使用更细粒度的刷新方法,如 notifyItemInserted
、notifyItemRemoved
和 notifyItemChanged
,而不是使用 notifyDataSetChanged
。notifyDataSetChanged
会导致整个列表重新布局和绑定数据,性能开销较大。
java
// 插入数据时使用notifyItemInserted方法
adapter.notifyItemInserted(position);
// 删除数据时使用notifyItemRemoved方法
adapter.notifyItemRemoved(position);
// 更新数据时使用notifyItemChanged方法
adapter.notifyItemChanged(position);
五、总结与展望
5.1 总结
RecyclerView 的缓存机制是其高性能的关键所在。通过四级缓存(mAttachedScrap
、mCachedViews
、mViewCacheExtension
和 mRecyclerPool
)的协同工作,RecyclerView 可以高效地复用视图,减少视图的创建和销毁次数,从而提高列表滚动的流畅性和性能。在数据更新时,缓存机制也能根据不同的更新操作进行相应的处理,确保显示的内容与数据一致。同时,通过合理设置缓存大小、优化视图创建和绑定逻辑、利用预取机制和避免不必要的刷新等性能优化方法,可以进一步提升 RecyclerView 的性能。
5.2 展望
随着 Android 技术的不断发展,RecyclerView 的缓存机制也可能会有进一步的优化和改进。例如,未来可能会引入更智能的缓存算法,根据列表项的使用频率和数据更新情况动态调整缓存大小和策略。同时,可能会加强与其他 Android 组件的集成,提供更便捷的缓存管理和性能优化方案。此外,对于复杂列表场景的支持也可能会得到进一步提升,如支持嵌套列表、多类型列表等的高效缓存和复用。总之,RecyclerView 的缓存机制将在未来的 Android 开发中继续发挥重要作用,并不断适应新的需求和挑战。