深度探秘!Android RecyclerView 缓存机制的底层原理全解析
深度探秘!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类型的变量,用于存储视图。
// RecyclerView类中定义mAttachedScrap ArrayList mAttachedScrap = new ArrayList();
当RecyclerView进行布局更新时,会调用detachAndScrapAttachedViews方法将当前屏幕上的视图分离并添加到mAttachedScrap中。
/** * 将当前屏幕上附着的视图分离并添加到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中。
// 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为例):
// 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中获取视图:
// 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类型的变量。
// Recycler类中定义mCachedViews ArrayList mCachedViews = new ArrayList(); // 缓存的默认最大容量 private static final int DEFAULT_CACHE_SIZE = 2;
当视图从屏幕上移除时,会被添加到mCachedViews中。在Recycler类的recycleViewHolderInternal方法中可以看到相关逻辑:
// 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()当需要获取视图时,会优先从mCachedViews中查找。在Recycler类的getViewForPosition方法中:
// 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:
// 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; i2.3 第三级缓存:mViewCacheExtension
2.3.1 mViewCacheExtension的作用
mViewCacheExtension是RecyclerView提供的一个扩展缓存接口,开发者可以实现该接口来自定义缓存逻辑,以满足特定的业务需求。它可以在mAttachedScrap和mCachedViews无法满足视图复用需求时发挥作用。
(图片来源网络,侵删)2.3.2 mViewCacheExtension的相关源码分析
在RecyclerView中,mViewCacheExtension是一个ViewCacheExtension类型的变量。
// RecyclerView类中定义mViewCacheExtension ViewCacheExtension mViewCacheExtension;ViewCacheExtension是一个抽象接口,定义如下:
(图片来源网络,侵删)// 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中获取:
// 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方法获取视图:
(图片来源网络,侵删)// 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类型的变量。
// Recycler类中定义mRecyclerPool RecycledViewPool mRecyclerPool;RecycledViewPool类中,使用一个SparseArray来存储不同类型的ViewHolder列表,SparseArray的键为视图类型,值为对应的ViewHolder列表。
// RecycledViewPool类 public class RecycledViewPool { // 存储不同类型ViewHolder列表的SparseArray private final SparseArray 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 scrapHeap = getScrapDataForType(viewType); if (mScrap.get(viewType) == null) { // 如果列表不存在,则创建一个新的列表 scrapHeap = new ArrayList(); mScrap.put(viewType, scrapHeap); } if (force || 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 getScrapDataForType(int viewType) { return mScrap.get(viewType); } }在Recycler类的getViewForPosition方法中,当从其他缓存都无法获取到视图时,会从mRecyclerPool中获取:
// 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 等方法,进而触发视图回收逻辑。
// 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 中。
// 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()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 方法中,会根据视图类型将其添加到相应的列表中。
// RecycledViewPool类的addViewHolderToRecycledViewPool方法 void addViewHolderToRecycledViewPool(@NonNull ViewHolder viewHolder, boolean force) { final int viewType = viewHolder.getItemViewType(); // 获取对应视图类型的ViewHolder列表 ArrayList scrapHeap = getScrapDataForType(viewType); if (mScrap.get(viewType) == null) { // 如果列表不存在,则创建一个新的列表 scrapHeap = new ArrayList(); mScrap.put(viewType, scrapHeap); } if (force || scrapHeap.size()3.2 视图复用流程
当需要显示新的列表项时,RecyclerView 会尝试从缓存中获取可用的视图进行复用。视图复用流程会依次从各级缓存中查找视图,直到找到合适的视图或创建一个新的视图。以下是详细的源码分析和流程说明。
3.2.1 视图复用的触发时机
视图复用通常在滚动操作、数据更新或布局变化等情况下触发。当需要显示新的列表项时,会调用 RecyclerView 的 getViewForPosition 方法来获取视图。
// 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 中获取视图。
// 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; i3.2.3 从 mCachedViews 中获取视图
如果从 mAttachedScrap 中未获取到视图,则会从 mCachedViews 中查找。在 getScrapOrHiddenOrCachedHolderForPosition 方法中,会遍历 mCachedViews 列表,找到与当前位置匹配的 ViewHolder。
// 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; i3.2.4 从 mViewCacheExtension 中获取视图
如果从 mAttachedScrap 和 mCachedViews 中都未获取到视图,则会尝试从 mViewCacheExtension 中获取。在 tryGetViewHolderForPositionByDeadline 方法中,会调用 mViewCacheExtension 的 getViewForPositionAndType 方法。
// 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 方法。
// 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 方法创建一个新的视图。
// 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 方法中,会处理数据更新事件。
// 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 方法中,会根据插入位置和缓存情况重新布局视图。
// LayoutManager的onLayoutChildren方法 public abstract void onLayoutChildren(@NonNull Recycler recycler, @NonNull State state);3.3.2 删除数据
当调用 RecyclerView.Adapter 的 notifyItemRemoved 方法删除数据时,RecyclerView 会将被删除位置的视图从屏幕上移除,并将其添加到相应的缓存中。在 RecyclerView 的 dispatchLayoutStep2 方法中,会处理数据更新事件。
// 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 方法中,会根据删除位置和缓存情况重新布局视图。
// LayoutManager的onLayoutChildren方法 public abstract void onLayoutChildren(@NonNull Recycler recycler, @NonNull State state);3.3.3 更新数据
当调用 RecyclerView.Adapter 的 notifyItemChanged 方法更新数据时,RecyclerView 会尝试从缓存中获取对应的视图,并更新其数据。如果缓存中没有对应的视图,则会重新创建一个视图并绑定新的数据。在 RecyclerView 的 dispatchLayoutStep2 方法中,会处理数据更新事件。
// 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 方法中,会根据更新位置和缓存情况重新布局视图。
// 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 的大小:
// 获取RecyclerView的Recycler对象 RecyclerView.Recycler recycler = recyclerView.getRecycler(); // 设置mCachedViews的大小 recycler.setViewCacheSize(5);4.1.2 mRecyclerPool 缓存大小设置
mRecyclerPool 的默认缓存大小为每个类型 5 个。如果列表项的类型较多或复用频率较高,可以适当增加 mRecyclerPool 的大小。可以通过以下方式设置 mRecyclerPool 的大小:
// 创建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 方法中进行简单的数据绑定。
// 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 提供了预取机制,可以在滚动过程中提前加载下一个或多个视图,以提高滚动的流畅性。可以通过以下方式启用预取机制:
// 创建LinearLayoutManager对象 LinearLayoutManager layoutManager = new LinearLayoutManager(context); // 启用预取机制 layoutManager.setItemPrefetchEnabled(true); // 设置预取的数量 layoutManager.setInitialPrefetchItemCount(2); // 将LayoutManager设置给RecyclerView recyclerView.setLayoutManager(layoutManager);4.4 避免不必要的刷新
在数据更新时,尽量使用更细粒度的刷新方法,如 notifyItemInserted、notifyItemRemoved 和 notifyItemChanged,而不是使用 notifyDataSetChanged。notifyDataSetChanged 会导致整个列表重新布局和绑定数据,性能开销较大。
// 插入数据时使用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 开发中继续发挥重要作用,并不断适应新的需求和挑战。