深度探秘!Android RecyclerView 缓存机制的底层原理全解析

深度探秘!Android RecyclerView 缓存机制的底层原理全解析

一、RecyclerView 缓存机制概述

1.1 缓存机制的重要性

在Android应用开发中,RecyclerView作为展示大量数据的核心组件,其性能优化至关重要。而缓存机制是提升RecyclerView性能的关键所在。通过缓存复用视图,避免了频繁的视图创建与销毁操作,显著减少了CPU和内存资源的消耗,从而提升了列表滚动的流畅性,为用户带来更顺滑的使用体验。例如,在新闻资讯类应用中,大量的新闻条目展示如果没有高效的缓存机制,会导致滚动卡顿,影响用户浏览新闻的体验。

1.2 缓存机制的核心目标

RecyclerView的缓存机制主要有两个核心目标:一是减少视图的创建和销毁次数,二是快速获取可用视图用于展示。通过缓存已创建的视图,在需要显示新的列表项时,优先从缓存中获取,而不是重新创建新视图,从而大大提高了数据展示的效率。

1.3 缓存机制的基本组成

RecyclerView的缓存机制主要由四级缓存构成,分别是mAttachedScrapmCachedViewsmViewCacheExtensionmRecyclerPool。每一级缓存都有其独特的功能和适用场景,它们相互协作,共同实现高效的视图缓存与复用。

二、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提供的一个扩展缓存接口,开发者可以实现该接口来自定义缓存逻辑,以满足特定的业务需求。它可以在mAttachedScrapmCachedViews无法满足视图复用需求时发挥作用。

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方法中,当从mAttachedScrapmCachedViews中都无法获取到视图时,会尝试从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方法中,调用mViewCacheExtensiongetViewForPositionAndType方法获取视图:

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 时,一些视图会移出屏幕范围,此时就需要对这些视图进行回收。在 RecyclerViewonTouchEvent 方法中处理滚动事件,当滚动距离达到一定阈值时,会调用 scrollByscrollToPosition 等方法,进而触发视图回收逻辑。

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 视图回收的具体实现

RecyclerViewRecycler 类中,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 中,需要满足以下条件:

  1. mViewCacheMax 大于 0,即缓存容量不为 0。
  2. ViewHolder 不包含 FLAG_INVALIDFLAG_REMOVEDFLAG_UPDATEFLAG_ADAPTER_POSITION_UNKNOWN 这些标志。
  3. mCachedViews 的数量未达到上限。
  4. 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 视图复用的触发时机

视图复用通常在滚动操作、数据更新或布局变化等情况下触发。当需要显示新的列表项时,会调用 RecyclerViewgetViewForPosition 方法来获取视图。

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 中获取视图

如果从 mAttachedScrapmCachedViews 中都未获取到视图,则会尝试从 mViewCacheExtension 中获取。在 tryGetViewHolderForPositionByDeadline 方法中,会调用 mViewCacheExtensiongetViewForPositionAndType 方法。

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 中获取视图

如果从 mAttachedScrapmCachedViewsmViewCacheExtension 中都未获取到视图,则会从 mRecyclerPool 中获取。在 Recycler 类的 getViewForPosition 方法中,会调用 mRecyclerPoolgetRecycledView 方法。

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.AdaptercreateViewHolder 方法创建一个新的视图。

java 复制代码
// RecyclerView.Adapter的createViewHolder方法
@NonNull
public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);

3.3 数据更新时的缓存处理

当 RecyclerView 的数据发生更新时,需要对缓存进行相应的处理,以确保显示的内容与数据一致。以下是不同数据更新操作时的缓存处理分析。

3.3.1 插入数据

当调用 RecyclerView.AdapternotifyItemInserted 方法插入数据时,RecyclerView 会根据插入位置和缓存情况进行处理。在 RecyclerViewdispatchLayoutStep2 方法中,会处理数据更新事件。

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);
}

LayoutManageronLayoutChildren 方法中,会根据插入位置和缓存情况重新布局视图。

java 复制代码
// LayoutManager的onLayoutChildren方法
public abstract void onLayoutChildren(@NonNull Recycler recycler, @NonNull State state);
3.3.2 删除数据

当调用 RecyclerView.AdapternotifyItemRemoved 方法删除数据时,RecyclerView 会将被删除位置的视图从屏幕上移除,并将其添加到相应的缓存中。在 RecyclerViewdispatchLayoutStep2 方法中,会处理数据更新事件。

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);
}

LayoutManageronLayoutChildren 方法中,会根据删除位置和缓存情况重新布局视图。

java 复制代码
// LayoutManager的onLayoutChildren方法
public abstract void onLayoutChildren(@NonNull Recycler recycler, @NonNull State state);
3.3.3 更新数据

当调用 RecyclerView.AdapternotifyItemChanged 方法更新数据时,RecyclerView 会尝试从缓存中获取对应的视图,并更新其数据。如果缓存中没有对应的视图,则会重新创建一个视图并绑定新的数据。在 RecyclerViewdispatchLayoutStep2 方法中,会处理数据更新事件。

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);
}

LayoutManageronLayoutChildren 方法中,会根据更新位置和缓存情况重新布局视图。

java 复制代码
// LayoutManager的onLayoutChildren方法
public abstract void onLayoutChildren(@NonNull Recycler recycler, @NonNull State state);

四、RecyclerView 缓存机制的性能优化

4.1 合理设置缓存大小

mCachedViewsmRecyclerPool 的缓存大小会影响 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.AdapteronBindViewHolder 方法中,尽量减少不必要的操作,如避免在该方法中进行耗时的计算或网络请求。可以将这些操作提前处理,只在 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 避免不必要的刷新

在数据更新时,尽量使用更细粒度的刷新方法,如 notifyItemInsertednotifyItemRemovednotifyItemChanged,而不是使用 notifyDataSetChangednotifyDataSetChanged 会导致整个列表重新布局和绑定数据,性能开销较大。

java 复制代码
// 插入数据时使用notifyItemInserted方法
adapter.notifyItemInserted(position);

// 删除数据时使用notifyItemRemoved方法
adapter.notifyItemRemoved(position);

// 更新数据时使用notifyItemChanged方法
adapter.notifyItemChanged(position);

五、总结与展望

5.1 总结

RecyclerView 的缓存机制是其高性能的关键所在。通过四级缓存(mAttachedScrapmCachedViewsmViewCacheExtensionmRecyclerPool)的协同工作,RecyclerView 可以高效地复用视图,减少视图的创建和销毁次数,从而提高列表滚动的流畅性和性能。在数据更新时,缓存机制也能根据不同的更新操作进行相应的处理,确保显示的内容与数据一致。同时,通过合理设置缓存大小、优化视图创建和绑定逻辑、利用预取机制和避免不必要的刷新等性能优化方法,可以进一步提升 RecyclerView 的性能。

5.2 展望

随着 Android 技术的不断发展,RecyclerView 的缓存机制也可能会有进一步的优化和改进。例如,未来可能会引入更智能的缓存算法,根据列表项的使用频率和数据更新情况动态调整缓存大小和策略。同时,可能会加强与其他 Android 组件的集成,提供更便捷的缓存管理和性能优化方案。此外,对于复杂列表场景的支持也可能会得到进一步提升,如支持嵌套列表、多类型列表等的高效缓存和复用。总之,RecyclerView 的缓存机制将在未来的 Android 开发中继续发挥重要作用,并不断适应新的需求和挑战。

相关推荐
_一条咸鱼_7 小时前
深度揭秘!Android HorizontalScrollView 使用原理全解析
android·面试·android jetpack
_一条咸鱼_7 小时前
揭秘 Android RippleDrawable:深入解析使用原理
android·面试·android jetpack
_一条咸鱼_7 小时前
深入剖析:Android Snackbar 使用原理的源码级探秘
android·面试·android jetpack
_一条咸鱼_7 小时前
揭秘 Android FloatingActionButton:从入门到源码深度剖析
android·面试·android jetpack
_一条咸鱼_7 小时前
深度剖析 Android SmartRefreshLayout:原理、源码与实战
android·面试·android jetpack
_一条咸鱼_7 小时前
揭秘 Android GestureDetector:深入剖析使用原理
android·面试·android jetpack
_一条咸鱼_7 小时前
深入探秘 Android DrawerLayout:源码级使用原理剖析
android·面试·android jetpack
_一条咸鱼_7 小时前
深度揭秘:Android CardView 使用原理的源码级剖析
android·面试·android jetpack
_一条咸鱼_7 小时前
惊爆!Android RecyclerView 性能优化全解析
android·面试·android jetpack
_一条咸鱼_7 小时前
探秘 Android RecyclerView 惯性滑动:从源码剖析到实践原理
android·面试·android jetpack