Android 源码浅析:RecyclerView 源码浅析(1)—— 回收、复用、预加载机制

2,447 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

目录

前言

本系列博客将分享 RecyclerView 源码相关知识,RecyclerView 是日常开发非常常用的 View,同时官方对其的设计也非常优秀值得我们去学习。

本博客从老生常谈的回收复用机制部分源码入手,从源码角度来理解回收复用、预加载机制。

源码版本

implementation 'androidx.recyclerview:recyclerview:1.2.1'

概述

切入点

我们知道当 RecyclerView 上下滑动时那些移出屏幕的 ViewHolder 会进行回收,新进入屏幕的 ViewHolder 会进行复用(如果有缓存),那么对于回收复用的源码分析就要从触摸方法中开始找切入点,先看一下 onTouchEvent 方法中处理滑动的调用流程:

RecyclerView.java

public boolean onTouchEvent(MotionEvent e) {
    // ...
    // mLayout 即为 LayoutManager 可以看出不设置 LayoutManager 是无法触发滑动的
    if (mLayout == null) {
        return false;
    }
    // ...
    switch (action) {
        case MotionEvent.ACTION_DOWN: {}
        case MotionEvent.ACTION_MOVE: {
            // ...
            // 根据 Down 事件中保存的信息计算出滑动距离
            int dx = mLastTouchX - x;
            int dy = mLastTouchY - y;
            // 滑动前先进行方向判断
            if (mScrollState != SCROLL_STATE_DRAGGING) {
                boolean startScroll = false;
                // ...
                if (canScrollVertically) { // 是否可以垂直滑动
                    if (dy > 0) {
                        dy = Math.max(0, dy - mTouchSlop);
                    } else {
                        dy = Math.min(0, dy + mTouchSlop);
                    }
                    if (dy != 0) {
                        startScroll = true;
                    }
                }
                if (startScroll) { // 标记开始滑动
                    setScrollState(SCROLL_STATE_DRAGGING);
                }
            }
            
            if (mScrollState == SCROLL_STATE_DRAGGING) {
                // ...
                // scrollByInternal 是处理滑动的方法 !!!
                if (scrollByInternal(
                        canScrollHorizontally ? dx : 0,
                        canScrollVertically ? dy : 0,
                        e, TYPE_TOUCH)) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                // ...
            }
        }
        // ...
}

// scrollByInternal 源码部分
boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
    // ...
    if (mAdapter != null) {
        mReusableIntPair[0] = 0;
        mReusableIntPair[1] = 0;
        // scrollStep 方法中计算出了滑动消耗的距离,并且赋值给了 mReusableIntPair
        scrollStep(x, y, mReusableIntPair);
        consumedX = mReusableIntPair[0];
        consumedY = mReusableIntPair[1];
        unconsumedX = x - consumedX;
        unconsumedY = y - consumedY;
    }
    // ...
}

// scrollStep 源码部分
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
    // ...
    int consumedX = 0;
    int consumedY = 0;
    // 不管是水平还是垂直滑动 最终都调用到了 LayoutManager 的 scrollXXXBy 方法中
    if (dx != 0) {
        consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
    }
    if (dy != 0) {
        consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
    }
    //...
    // 赋值
    if (consumed != null) {
        consumed[0] = consumedX;
        consumed[1] = consumedY;
    }
}

从上面源码部分中可以看出,RecyclerView 的滑动最终是交给 LayoutManager 去处理,我们以 LinearLayoutManager 为例继续跟踪源码:

LinearLayoutManager.java

public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
        RecyclerView.State state) {
    // ...
    return scrollBy(dx, recycler, state);
}

public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
        RecyclerView.State state) {
    // ...
    return scrollBy(dy, recycler, state);
}

不论是水平还是垂直滑动都调用到了 scrollBy 方法,继续跟踪源码:

LinearLayoutManager.java

int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
    // ...
    // 注意这里这个 fill 方法
    final int consumed = mLayoutState.mScrollingOffset
            + fill(recycler, mLayoutState, state, false);
    // ...
    return scrolled;
}

fill 是填充的意思,当发生滑动时,就需要有新的 View 来填充滑动的部分,来看一下 fill 的源码:

LinearLayoutManager.java

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    final int start = layoutState.mAvailable;
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        // ...
        // 回收 
        recycleByLayoutState(recycler, layoutState);
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        // ...
        // 复用
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        // ...
    }
}

在 RecyclerView 滑动时回收和复用操作会同时触发,有移除屏幕的 ViewHolder 就需要有新的 ViewHolder 来填补,这个不难理解。

下面来分别看下回收和复用的方法调用流程。

recycleByLayoutState(recycler, layoutState);

LinearLayoutManager.java

private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
    // ...
    if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
        // 回收头部 View
        recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
    } else {
        // 回收尾部 View 两个方法逻辑都一样
        recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
    }
}

// recycleViewsFromEnd 和 recycleViewsFromStart 逻辑整体一样 就只贴一个了
private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
        int noRecycleSpace) {
    final int childCount = getChildCount();
    // ...
    // 是否倒置 (LinearLayoutManager 构造器中可以设置)
    // 无论 if 还是 else 最终都调用到了 recycleChildren
    if (mShouldReverseLayout) {
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedStart(child) < limit
                    || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                // 根据 i 来回收 View
                recycleChildren(recycler, 0, i);
                return;
            }
        }
    } else {
        for (int i = childCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            if (mOrientationHelper.getDecoratedStart(child) < limit
                    || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                // 根据 i 来回收 View
                recycleChildren(recycler, childCount - 1, i);
                return;
            }
        }
    }
}

private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
    // ...
    // 最终调用到了 removeAndRecycleViewAt 方法
    if (endIndex > startIndex) {
        for (int i = endIndex - 1; i >= startIndex; i--) {
            removeAndRecycleViewAt(i, recycler);
        }
    } else {
        for (int i = startIndex; i > endIndex; i--) {
            removeAndRecycleViewAt(i, recycler);
        }
    }
}

LayoutManager.java

public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
    final View view = getChildAt(index); //根据 index 找到 View
    removeViewAt(index); // 从 RV 中移除
    recycler.recycleView(view); // 交给 recycler 进行回收
}

layoutChunk(recycler, state, layoutState, layoutChunkResult);

LinearLayoutManager.java

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    View view = layoutState.next(recycler);
    // ...
}

View next(RecyclerView.Recycler recycler) {
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    // 最终通过 recycler 的 getViewForPosition 方法获取到了 View
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

Recycler

从上述的源码调用流程中可以看出,回收复用的具体逻辑都放在了 Recycler 中,Recycler 和 LayoutManager 一样是 RecyclerView 的内部类,先来看下 Recycler 中的一些定义:

RecyclerView.java

public final class Recycler {
    // 一级缓存
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;
    
    // 二级缓存
    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

    // 三级缓存
    private ViewCacheExtension mViewCacheExtension;

    // 四级缓存
    RecycledViewPool mRecyclerPool;
    // ...
}

在这里先对这些定义有个大致的了解:

  1. mAttachedScrap、mChangedScrap 屏幕上的 ViewHolder 缓存;
  2. mCachedViews 刚刚移除屏幕的 ViewHolder 会加入进来,默认容量为 2,超出容量后最先放入的会移出,并且加入 mRecyclerPool 缓存池中;
  3. mViewCacheExtension 供开发者自定义的缓存,一般不实现(在回收源码流程中并没有使用);
  4. mRecyclerPool 缓存池;

RecycledViewPool

mAttachedScrap、mCachedViews 就是简单的 ArrayList,存储 ViewHolder,RecycledViewPool 就需要查看源码来了解下了:

RecycledView.java

public static class RecycledViewPool {
    private static final int DEFAULT_MAX_SCRAP = 5;
    static class ScrapData {
        final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
        int mMaxScrap = DEFAULT_MAX_SCRAP; // mScrapHeap 的容量 默认为 5
        long mCreateRunningAverageNs = 0;
        long mBindRunningAverageNs = 0;
    }
    // 缓存池容器
    SparseArray<ScrapData> mScrap = new SparseArray<>();
    // ...
}

可以看出 RecycledViewPool 内部的容器为 SparseArray,存储类型是其内部类 ScrapData,而 ScrapData 内部使用 ArrayList 来存储 ViewHolder。当 RecyclerView 多类型 item 进行缓存时,直接以 itemViewType 作为 key,ScrapData 作为 value 对 ViewHolder 进行缓存,默认情况下每种不同的 ViewHolder 可以分别缓存 5 个。

以上部分对回收复用的源码调用流程以及四级缓存的容器有了一些简单了解,下面就来详细了解下回收复用的源码。

回收

回收源码从 Recycler.recycleView(view) 直接看起:

RecyclerView.java

public void recycleView(@NonNull View view) {
    // View 的 LayoutParams 为 RecyclerView.LayoutParams 
    // 从其内部可以直接获取到 ViewHolder
    ViewHolder holder = getChildViewHolderInt(view);
    if (holder.isTmpDetached()) {
        // 清除动画资源等 并且触发 adapter 的 onViewDetachedFromWindow 方法
        removeDetachedView(view, false);
    }
    // 如果 ViewHolder 存在于 mAttachedScrap 或者 mChangedScrap 中就移除、清除相关 flag
    if (holder.isScrap()) {
        holder.unScrap();
    } else if (holder.wasReturnedFromScrap()) {
        holder.clearReturnedFromScrapFlag();
    }
    // 回收核心方法
    recycleViewHolderInternal(holder);
    // 停止动画
    if (mItemAnimator != null && !holder.isRecyclable()) {
        mItemAnimator.endAnimation(holder);
    }
}

void recycleViewHolderInternal(ViewHolder holder) {
    // ...
    final boolean transientStatePreventsRecycling = holder
            .doesTransientStatePreventRecycling();
    // 这里提一下 可以重写 adapter 的 onFailedToRecycleView 方法返回 ture 来达到强制回收 ViewHolder
    final boolean forceRecycle = mAdapter != null
            && transientStatePreventsRecycling
            && mAdapter.onFailedToRecycleView(holder); 
    boolean cached = false;
    boolean recycled = false;
    // ...
    // 如果可以回收
    if (forceRecycle || holder.isRecyclable()) {
        if (mViewCacheMax > 0
                && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                | ViewHolder.FLAG_REMOVED
                | ViewHolder.FLAG_UPDATE
                | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
            // 先获取 mCachedViews 的 size
            int cachedViewSize = mCachedViews.size();
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                // 超出容量后移除头部的 item
                // 注意这个方法 !!! 内部还将移除的 ViewHolder 加入到了 RecyclerViewPool 中
                recycleCachedViewAt(0);
                cachedViewSize--;
            }
    
            int targetCacheIndex = cachedViewSize;
            // 这个 if 中是预加载机制相关代码 后面解释
            if (ALLOW_THREAD_GAP_WORK
                    && cachedViewSize > 0
                    && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                // when adding the view, skip past most recently prefetched views
                int cacheIndex = cachedViewSize - 1;
                while (cacheIndex >= 0) {
                    int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                    if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                        break;
                    }
                    cacheIndex--;
                }
                targetCacheIndex = cacheIndex + 1;
            }
            // 添加到 mCachedViews 缓存中
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {
            // 如果没有添加到 mCachedView 中则直接加入 RecycledViewPool 中
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
        // ...
        // 不能回收就什么也不做
    }
    // 清除保存的 ViewHolder 动画相关的信息
    mViewInfoStore.removeViewHolder(holder);
    if (!cached && !recycled && transientStatePreventsRecycling) {
        holder.mBindingAdapter = null;
        holder.mOwnerRecyclerView = null;
    }
}

void recycleCachedViewAt(int cachedViewIndex) {
    // 根据 index 获取到要从 mCachedViews 中移除的 ViewHolder
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    // 添加到 RecycledViewPool
    addViewHolderToRecycledViewPool(viewHolder, true);
    // 从 mCachedViews 移除
    mCachedViews.remove(cachedViewIndex);
}

void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
    View itemView = holder.itemView;
    // ...
    if (dispatchRecycled) {
        // 触发 adapter 的 onViewRecycled 方法
        dispatchViewRecycled(holder);
    }
    // ...
    // 添加到缓存池
    getRecycledViewPool().putRecycledView(holder);
}

public void putRecycledView(ViewHolder scrap) {
    // 根据 itemViewType 获取缓存池中对应的 ArrayList
    // 没有的话会 new 一个 ScrapData 添加到 mScrap 中
    final int viewType = scrap.getItemViewType();
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
    // 判断是否超出容量
    if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
        return;
    }
    // ...
    // 清除 ViewHolder 的状态信息
    scrap.resetInternal();
    // 添加到缓存池
    scrapHeap.add(scrap);
}

注释中有详细的解释,在这里就简单总结下整体回收的流程:

  1. 如果要回收的 ViewHolder 存在于一级缓存 mAttachedScrap、mChangedScrap 中则从中移除;
  2. 将 ViewHolder 加入二级缓存 mCacheViews 中,如果容量超出则移除 index 为 0 的 ViewHolder,并且将其添加到 RecyclerViewPool 中;
  3. 如果 ViewHolder 在第 2 步中没有成功添加到 mCacheViews 中则直接添加进 RecyclerViewPool 缓存;
  4. RecyclerViewPool 中会根据 ItemViewType 获取对应 ViewHolder 的 ArrayList 容器(如果还没初始化则直接 new 一个)判断容量后添加进去,添加前会清除 ViewHolder 的一些状态信息;

复用

熟悉了回收流程后趁热打铁来看一下复用的源码流程,从 Recycler.getViewForPosition(position) 直接看起:

RecyclerView.java

public View getViewForPosition(int position) {
    // 调用了重载方法
    return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
    // 调用了 tryGetViewHolderForPositionByDeadline 获取到 ViewHolder
    // 将 ViewHolder 的 itemView 返回
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

// 复用机制的核心源码
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    // ...
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    // 如果是预布局状态通过一级缓存 mChangedScrap 中查找
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    // 二级缓存 内部会先从 mAttachedScrap 中尝试获取,获取不到则尝试从 mCacheViews 中寻找
    // 注意这里是通过 position 寻找
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        // ...
    }
    // 二级缓存 跟上面的区别是这次根据 mHasStableIds 寻找
    // adapter 重写 getItemId 方法设置 id
    if (holder == null) {
        // ...
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            // ...
        }
        
        // 三级缓存 自定义缓存
        if (holder == null && mViewCacheExtension != null) {
            // 注意这里返回的是 View
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                // 通过 RecyclerView.LayoutParams 获取其 ViewHolder
                holder = getChildViewHolder(view);
                // ...
            }
        }
        
        // 四级缓存
        if (holder == null) { // fallback to pool
            holder = getRecycledViewPool().getRecycledView(type);
            // ...
        }
        
        // 四级缓存都没有获取到
        if (holder == null) {
            // ...
            // 通过 adapter onCreateViewHolder 方法创建 ViewHolder
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            // 预加载机制相关代码 后面解释
            if (ALLOW_THREAD_GAP_WORK) {
                // only bother finding nested RV if prefetching
                RecyclerView innerView = findNestedRecyclerView(holder.itemView);
                if (innerView != null) {
                    holder.mNestedRecyclerView = new WeakReference<>(innerView);
                }
            }
            // ...
            }
        }
    }
    // ...
    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        // 如果没有绑定过数据 则调用 adapter onBindViewHolder 进行绑定
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    
    // 给 ViewHolder 的 itemView 设置 LayoutParams
    final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
    // 这里的 LayoutParams 是 RecyclerView.LayoutParams
    final LayoutParams rvLayoutParams;
    if (lp == null) {
        // LayoutParams 由 LayoutManager 返回
        rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else if (!checkLayoutParams(lp)) {
        rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else {
        rvLayoutParams = (LayoutParams) lp;
    }
    rvLayoutParams.mViewHolder = holder; // 将 ViewHolder 赋值给 LayoutParams 
    rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
    return holder;
}

总结下,源码中四级缓存的方法分别为:

  1. 一级缓存 getChangedScrapViewForPosition,根据 position 从 mChangedScrap 中获取;
  2. 二级缓存 getScrapOrHiddenOrCachedHolderForPosition、getScrapOrCachedViewForId,优先根据 position 从 mAttachScrap、mCacheViews 中获取,其次根据 itemId 再次从 mAttachScrap、mCacheViews 中获取;
  3. 三级缓存 mViewCacheExtension.getViewForPositionAndType,自定义缓存,一定注意这里返回的是 View;
  4. 四级缓存 getRecycledViewPool().getRecycledView,根据 itemViewType 获取对应 ViewHolder 的缓存 ArrayList,再从其中尝试获取。

每级缓存对应的方法源码比较易懂,篇幅原因就不挨着贴出来了。

预加载

image.png 预加载是 SDK 21 后对 RecyclerView 的一项优化。

在了解与加载机制之前,需要先简单了解下 Android 开启硬件加速后的 UI 渲染流程:

image.png 1. 当 VSync 信号到来后 UI Thread 处理输入事件、动效、测量、布局、绘制等等,将这些封装成 Render Node 同步给 Render Thread; 2. Render Thread 会将这些绘制指令转为 OpenGL 指令,最终通过 GPU 绘制到 Graphic Buffer; 3. SurfaceFlinger 拿到 buffer 后进行 layer 合并,最终将数据交给 HAL 层绘制到设备屏幕上; 4. 当下一次 VSync 信号到来后重复上述流程;

UI Thread 将 Render Node 同步给 Render Thread 后等待的过程就是预加载要利用的空隙:

image.png

原理大概就是这样,具体来看一下源码中是如何实现:

RecyclerView.java

protected void onAttachedToWindow() {
    // ...
    // SDK >= 21
    if (ALLOW_THREAD_GAP_WORK) {
        // sGapWorker 是一个 ThreadLocal
        mGapWorker = GapWorker.sGapWorker.get();
        if (mGapWorker == null) { //初始化 GapWorker
            mGapWorker = new GapWorker();
            // ...
            GapWorker.sGapWorker.set(mGapWorker);
        }
        mGapWorker.add(this);
    }
}

GapWorker 实现了 Runnable 接口,GapWorker 的使用在 onTouchEvent 的 MOVE 事件中:

RecyclerView.java

public boolean onTouchEvent(MotionEvent e) {
    // ...
    switch (action) {
        // ...
        case MotionEvent.ACTION_MOVE: {
                // ...
            if (mGapWorker != null && (dx != 0 || dy != 0)) {
                mGapWorker.postFromTraversal(this, dx, dy);
            }
        }
}

来看一下 postFromTraversal 源码:

GapWorker.java

void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
    if (recyclerView.isAttachedToWindow()) {
        // ...
        if (mPostTimeNs == 0) {
            mPostTimeNs = recyclerView.getNanoTime();
            // GapWorker 实现了 Runnable
            // 通过 View.post 执行 run 方法
            recyclerView.post(this);
        }
    }
    // ...
}

继续查看 GapWorker 的 run 方法实现:

GapWorker.java

public void run() {
    try {
        // Trace
        Trace.beginSection(RecyclerView.TRACE_PREFETCH_TAG);

        long lastFrameVsyncNs = TimeUnit.MILLISECONDS.toNanos(
                mRecyclerViews.get(0).getDrawingTime());
        if (lastFrameVsyncNs == 0) {
            return;
        }
        // 获取下次 VSync 信号到来的时间
        long nextFrameNs = lastFrameVsyncNs + mFrameIntervalNs;
        // 尝试预加载
        prefetch(nextFrameNs);
    }
    // ...
}

void prefetch(long deadlineNs) {
    buildTaskList(); // 构建任务
    flushTasksWithDeadline(deadlineNs); // 执行
}

private void flushTasksWithDeadline(long deadlineNs) {
    for (int i = 0; i < mTasks.size(); i++) {
        final Task task = mTasks.get(i);
        if (task.view == null) {
            break; // done with populated tasks
        }
        // 循环执行
        flushTaskWithDeadline(task, deadlineNs);
        task.clear();
    }
}

private void flushTaskWithDeadline(Task task, long deadlineNs) {
    long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
    // prefetchPositionWithDeadline 方法中进行了预加载
    RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
            task.position, taskDeadlineNs);
    // ...
    }
}

private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
        int position, long deadlineNs) {
    // ...
    RecyclerView.Recycler recycler = view.mRecycler;
    // 这里又调用到了 tryGetViewHolderForPositionByDeadline
    RecyclerView.ViewHolder holder = recycler.tryGetViewHolderForPositionByDeadline(
            position, false, deadlineNs);
    
    // 预加载创建 View 成功之后
    if (holder != null) {
        if (holder.isBound()) { // 绑定数据成功 放入 mCacheViews 中
            recycler.recycleView(holder.itemView);
        } else { // 如果仅仅创建成功 没绑定数据 就放入 RecyclerViewPool 中
            recycler.addViewHolderToRecycledViewPool(holder, false);
        }
    }
    return holder;
}

再次回到 方法中:

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    if (holder == null) {
        long start = getNanoTime();
        // 这里是根据 ViewHolder 的平均创建时间判断
        // 判断是否有足够的时间进行预加载操作
        if (deadlineNs != FOREVER_NS
                && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
            // abort - we have a deadline we can't meet
            return null;
        }
        // 时间够 则预加载成功
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
        // ...
    }
    // ...
    // 利用剩余的时间尝试绑定数据
    bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    // ...
}

可以看出预加载之后对 ViewHolder 的缓存和前面回收复用部分是相符合的。预加载机制一般不需要我们额外编写代码即可实现,这部分内容就当扩展了解吧。

最后

本博客的重点内容为 RecyclerView 的回收复用机制,预加载机制讲的并不是很详细,其中设计的 Android 渲染流程也是非常深的一项知识,后续有机会再分享吧。

如果我的博客分享对你有点帮助,不妨点个赞支持下!