Android-RecyclerView布局显示和回收复用流程

一、RecyclerView的四级缓存

(1)mChangedScrap、mAttachedScrap:

用于屏幕内ItemView的快速重用。
Scrap缓存,其实就是用来保存被RecyclerView移除但是最近又马上要使用的缓存,比如RecyclerView自带的item的动画等,Scrap有两个,一个是mChangedScrap,一个是mAttachedScrap,这两个的区别就是保存的对象有些不一样。一般调用adapter的notifyItemRangeChanged被移除的holder保存在mChangedScrap中,其他的notify系列方法(不包括notifyDataSetChanged)移除的holder保存在mAttachedScrap中。这两个缓存是在布局阶段使用的,其他时候是空的。布局完成之后,这两个缓存中的holder就会移动到mCacheView或者RecyclerViewPool中。
mChangedScrap一般是在预布局阶段使用的(dispatchLayoutStep1()方法),而mAttachedScrap是在整个布局阶段使用的。
mAttachedScrap 保存依附于 RecyclerView 的 ViewHolder。包含移出屏幕但未从 RecyclerView 移除的 ViewHolder。就是未与RecyclerView分离的ViewHolder。这里的是ViewHolder的数据没有发生变化的
mChangedScrap 保存数据发生改变的 ViewHolder,即调用 notifyDataSetChanged() 等系列方法后需要更新的 ViewHolder。就是与RecyclerView分离的ViewHolder。这里的是ViewHolder的数据发生变化的,需要重新走Adapter的bind的

  • 在onLayout布局阶段,在LayoutManager.onLayoutChildren方法中会调用detachAndScrapAttachedViews方法,在这里又会调用scrapOrRecycleView方法,接着判断条件,如果调用了Recycler.scrapView方法,就会将ViewHolder回到到scrap中。
  • 在复用阶段,在tryGetViewHolderForPositionByDeadline方法中调用getScrapOrHiddenOrCachedHolderForPosition方法时,会将隐藏的View回收到一级缓存中,即隐藏但是没有被删除的View。即在dryRun为false的时候,获取隐藏的但是没有被remove的View,调用scrapView方法保存在mAttachedScrap或者mChangedScrap(mChangedScrap还有就是在预布局阶段加入)

(2)mCachedViews

默认上限为2个,缓存屏幕外2个ItemView。mCachedViews中保存的是有数据的ViewHolder。

  • 在重新布局时,会将ViewHolder添加到mCachedViews中,也是在onLayoutChildren方法中调用detachAndScrapAttachedViews,但是在scrapOrRecycleView方法中判断不同,则会调用Recycler.recycleViewHolderInternal方法回收
  • 在复用阶段,如果从一级缓存中取出的ViewHolder是不可用的,则会调用recycleViewHolderInternal方法,将ViewHolder放入到二级缓存中
    在这里存放的是detachView之后的视图,它里面存放的是已经remove掉的视图,已经和RV分离的关系的视图,但是它里面的ViewHolder依然保存着之前的信息,比如position、和绑定的数据等等。这一级缓存是有容量限制的,默认是2

(3)mViewCacheExtension

默认不实现的,这个是需要开发者自定义实现的缓存机制

(4)mRecyclerViewPool

缓存池,同一种ViewType的ViewHolder缓存默认上限为5个。保存的只是ViewHolder,但是这个ViewHolder没有数据,当RecyclerViewPool缓存的ViewHolder已经满了,则不会再加入了。

  • 在调用recycleViewHolderInternal回收加入到mRecyclerViewPool的时候,会先判断是否可以加入到mCachedViews,如果不满足加入到mCachedViews的条件,则会加入到mRecyclerViewPool中。而添加四级缓存,可能是在回收流程中,也可能是在复用流程中,如果是在回收流程中,则是在调用Recycler.recycleViewHolderInternal方法的时候,调用了addViewHolderToRecycledViewPool方法;如果是在复用流程,则是在tryGetViewHolderForPositionByDeadline方法中有两次,一次就是通过position寻找ViewHolder的时候,如果找到的是不可用的,则会调用Recycler.recycleViewHolderInternal可能又会进入第四级缓存,一次就是通过ID找到ViewHolder,如果还是不可用则会调用recycleCachedViewAt方法,进而调用addViewHolderToRecycledViewPool加入四级缓存
缓存 涉及对象 作用 重新创建视图View(onCreateViewHolder) 重新绑定数据(onBindViewHolder)
一级缓存 mAttachedScrap 缓存屏幕中可见范围的ViewHolder false false
一级缓存 mChangedScrap 缓存屏幕中可见范围的但是数据发生改变ViewHolder false true
二级缓存 mCachedViews 缓存滑动时即将与RecyclerView分离的ViewHolder,按子View的position或id缓存,默认最多存放2个 false false
三级缓存 mViewCacheExtension 开发者自行实现的缓存 - -
四级缓存 mRecyclerPool ViewHolder缓存池,本质上是一个SparseArray,其中key是ViewType(int类型),value存放的是 ArrayList< ViewHolder>,默认每个ArrayList中最多存放5个ViewHolder false true

二、RecyclerView的预取功能(预加载功能)

Android系统是通过每16ms刷新一次页面来保证UI的流畅性,现在Android系统中刷新UI会通过CPU产生数据,然后交给GPU渲染的形式来完成,这样当CPU完成数据交给GPU之后,此时还没到16ms,那么这段时间CPU就会处于空闲状态,需要等待下一帧才会进行数据处理,这样就白白浪费了空闲时间,所以在API25开始,RecyclerView内部就实现了预取功能。
RecyclerView的预取功能是依赖于GapWorker,通过每次的MOVE事件中,来判断是否预取下一个可能要显示的Item数据,判断的依据主要就是通过传入的dx和dy得到手指接下来可能要滑动的方向,如果dx或者dy的偏移量会导致下一个item要被显示出来则预取出来,但是并不是说预取下一个可能要显示的item一定都是成功的。
其实每次rv取出要显示的一个item本质上就是取出一个viewholder,根据viewholder上关联的itemview来展示这个item。而预取出viewholder最核心的方法就是

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs)

deadlineNs的取值有两种,一种是为了兼容api25之前的没有预取功能的情况,一种就是实际的deadlineNs数值,超过这个deadline则表示预取失败,预取机制主要目的就是提高RecyclerView整体滑动的流畅性,所以有限制主要是为了保证整体流畅性。

RecyclerView.onTouch

            case MotionEvent.ACTION_MOVE: {
                final int index = e.findPointerIndex(mScrollPointerId);
                if (index < 0) {
                    Log.e(TAG, "Error processing scroll; pointer index for id "
                            + mScrollPointerId + " not found. Did any MotionEvents get skipped?");
                    return false;
                }

                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 (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];
                }

                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 (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            vtev)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    // 执行预取功能
                    if (mGapWorker != null && (dx != 0 || dy != 0)) {
                        mGapWorker.postFromTraversal(this, dx, dy);
                    }
                }
            } break;

GapWorker.postFromTraversal

    void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
        if (recyclerView.isAttachedToWindow()) {
            if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) {
                throw new IllegalStateException("attempting to post unregistered view!");
            }
            // 判断第一次拖动时,是否将runnable交给Mainhandler里面,等待UI thread执行完成再执行prefetch
            // GapWorker其实就是一个Runnable的实现类
            if (mPostTimeNs == 0) {
                mPostTimeNs = recyclerView.getNanoTime();
                recyclerView.post(this);
            }
        }
        // 后续动作触发去更新最新的dx和dy,prefetch会按照最新的dx和dy计算prefetch的item的position
        recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
    }

GapWorker.run

    @Override
    public void run() {
        try {
            TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);
            // 这里是因为存在嵌套RecyclerView的情况
            if (mRecyclerViews.isEmpty()) {
                // abort - no work to do
                return;
            }

            // Query most recent vsync so we can predict next one. Note that drawing time not yet
            // valid in animation/input callbacks, so query it here to be safe.
            final int size = mRecyclerViews.size();
            long latestFrameVsyncMs = 0;
            // 获取RecyclerView最近一次开始RenderThread的时间
            for (int i = 0; i < size; i++) {
                RecyclerView view = mRecyclerViews.get(i);
                if (view.getWindowVisibility() == View.VISIBLE) {
                    latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs);
                }
            }

            if (latestFrameVsyncMs == 0) {
                // abort - either no views visible, or couldn't get last vsync for estimating next
                return;
            }

            // 计算预加载的最后时间,如果能在截止时间之前完成预加载,那么就可以成功完成ViewHolder的预加载
            // 否则就是预加载失败
            long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;

            prefetch(nextFrameNs);

            // TODO: consider rescheduling self, if there's more work to do
        } finally {
            mPostTimeNs = 0;
            TraceCompat.endSection();
        }
    }

GapWorker.prefetch

    void prefetch(long deadlineNs) {
        // 计算预加载任务列表
        buildTaskList();
        // 开始预加载
        flushTasksWithDeadline(deadlineNs);
    }

GapWorker.buildTaskList

    private void buildTaskList() {
        // Update PrefetchRegistry in each view
        final int viewCount = mRecyclerViews.size();
        int totalTaskCount = 0;
        for (int i = 0; i < viewCount; i++) {
            RecyclerView view = mRecyclerViews.get(i);
            if (view.getWindowVisibility() == View.VISIBLE) {
                view.mPrefetchRegistry.collectPrefetchPositionsFromView(view, false);
                totalTaskCount += view.mPrefetchRegistry.mCount;
            }
        }

        // Populate task list from prefetch data...
        mTasks.ensureCapacity(totalTaskCount);
        int totalTaskIndex = 0;
        for (int i = 0; i < viewCount; i++) {
            RecyclerView view = mRecyclerViews.get(i);
            if (view.getWindowVisibility() != View.VISIBLE) {
                // Invisible view, don't bother prefetching
                continue;
            }

            LayoutPrefetchRegistryImpl prefetchRegistry = view.mPrefetchRegistry;
            final int viewVelocity = Math.abs(prefetchRegistry.mPrefetchDx)
                    + Math.abs(prefetchRegistry.mPrefetchDy);
            for (int j = 0; j < prefetchRegistry.mCount * 2; j += 2) {
                final Task task;
                if (totalTaskIndex >= mTasks.size()) {
                    // 针对每个预加载的ViewHolder创建一个Task
                    task = new Task();
                    mTasks.add(task);
                } else {
                    task = mTasks.get(totalTaskIndex);
                }
                final int distanceToItem = prefetchRegistry.mPrefetchArray[j + 1];

                task.immediate = distanceToItem <= viewVelocity;
                task.viewVelocity = viewVelocity;
                task.distanceToItem = distanceToItem;
                task.view = view;
                task.position = prefetchRegistry.mPrefetchArray[j];

                totalTaskIndex++;
            }
        }

        // ... and priority sort
        Collections.sort(mTasks, sTaskComparator);
    }

GapWorker.flushTasksWithDeadline

    private void flushTasksWithDeadline(long deadlineNs) {
        // 遍历所有的Task开始预加载
        for (int i = 0; i < mTasks.size(); i++) {
            final Task task = mTasks.get(i);
            // 当task中的view==null的时候完成预加载任务
            if (task.view == null) {
                break; // done with populated tasks
            }
            flushTaskWithDeadline(task, deadlineNs);
            task.clear();
        }
    }

GapWorker.flushTaskWithDeadline

    private void flushTaskWithDeadline(Task task, long deadlineNs) {
        long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
        // 如果没能在deadlineNs之前构造好ViewHolder,则预加载失败
        // 在prefetchPositionWithDeadline中会调用RecyclerView.tryGetViewHolderForPositionByDeadline进行预加载
        // 这里是预取position位置的ViewHolder,在截止日期之前
        RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
                task.position, taskDeadlineNs);
        if (holder != null
                && holder.mNestedRecyclerView != null
                && holder.isBound()
                && !holder.isInvalid()) {
            // 预取RecyclerView内部的ViewHolder,这是处理嵌套RecyclerView的情况
            // 当外部预取的ViewHolder不是无效的,且是被绑定的
            prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
        }
    }

在RecyclerView.tryGetViewHolderForPositionByDeadline方法中会根据dx和dy以及当前滑动的方向计算预加载的position,dx和dy是在RecyclerView.onTouchEvent中滑动动态更新的,

三、RecyclerView的ViewHolder的复用

RecyclerView的ViewHolder的复用,其实就是从缓存中取出相应的ViewHolder来重新使用。
复用流程包括手指滑动的时候和requestLayout()的
而具体的复用流程其实都是依赖于RecyclerView.Recycler来实现的。


RecyclerView复用机制流程.png

1.手指滑动在onTouchEvent中触发

(1)RecyclerView.onTouchEvent

还是与预加载部分累死你,其实就是在预加载之前,通过调用scroollByInternal,开始判断缓存中是否有可以复用的ViewHolder

                if (mScrollState == SCROLL_STATE_DRAGGING) {
                    mLastTouchX = x - mScrollOffset[0];
                    mLastTouchY = y - mScrollOffset[1];

                    if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            vtev)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    if (mGapWorker != null && (dx != 0 || dy != 0)) {
                        mGapWorker.postFromTraversal(this, dx, dy);
                    }
                }
(2)RecyclerView.scrollByInternal
    boolean scrollByInternal(int x, int y, MotionEvent ev) {
        ...
        if (mAdapter != null) {
            // 执行滑动过程,在scrollStep中会调用mLayout.scrollVerticallyBy或者mLayout.scrollHorizontallyBy
            // mLayout其实就是LayoutManager的实现类对象,比如以LinearLayoutManager举例
            scrollStep(x, y, mScrollStepConsumed);
            consumedX = mScrollStepConsumed[0];
            consumedY = mScrollStepConsumed[1];
            unconsumedX = x - consumedX;
            unconsumedY = y - consumedY;
        }
        ...
        return consumedX != 0 || consumedY != 0;
    }
(3)LinearLayoutManager.scrollVerticallyBy
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == HORIZONTAL) {
            return 0;
        }
        return scrollBy(dy, recycler, state);
    }
(4)LinearLayoutManager.scrollBy
    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || dy == 0) {
            return 0;
        }
        mLayoutState.mRecycle = true;
        ensureLayoutState();
        final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        final int absDy = Math.abs(dy);
        updateLayoutState(layoutDirection, absDy, true, state);
        // 调用fill填充LayoutState定义的给定布局,
        // 在fill中主要就是做了两件事:回收ViewHolder到缓存中;从缓存中取出ViewHolder进行复用
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
        if (consumed < 0) {
            if (DEBUG) {
                Log.d(TAG, "Don't have any more elements to scroll");
            }
            return 0;
        }
        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
        mOrientationHelper.offsetChildren(-scrolled);
        if (DEBUG) {
            Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
        }
        mLayoutState.mLastScrollDelta = scrolled;
        return scrolled;
    }

在fill中主要会调用两个方法:recycleByLayoutState和layoutChunk,recycleByLayoutState主要的目的是用来回收ViewHolder的,而layoutChunk主要的目的就是从缓存中取出ViewHolder进行复用

(5)LinearLayoutManager.fill()
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        // max offset we should set is mFastScroll + available
        final int start = layoutState.mAvailable;
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            // TODO ugly bug fix. should not happen
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            // 首先调用recycleByLayoutState回收ViewHolder
            // 这个回收是从二级缓存开始回收,即回收的时候,最少都是将回收的ViewHolder加入到mCacheViews中
            recycleByLayoutState(recycler, layoutState);
        }
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.beginSection("LLM LayoutChunk");
            }
            // 执行复用,layoutChunk的过程其实就是从缓存中取出View、或者创建View,最后调用addView
            // 遍历调用layoutChunk
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.endSection();
            }
            if (layoutChunkResult.mFinished) {
                break;
            }
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            /**
             * Consume the available space if:
             * * layoutChunk did not request to be ignored
             * * OR we are laying out scrap children
             * * OR we are not doing pre-layout
             */
            if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                    || !state.isPreLayout()) {
                layoutState.mAvailable -= layoutChunkResult.mConsumed;
                // we keep a separate remaining space because mAvailable is important for recycling
                remainingSpace -= layoutChunkResult.mConsumed;
            }

            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
            if (stopOnFocusable && layoutChunkResult.mFocusable) {
                break;
            }
        }
        if (DEBUG) {
            validateChildOrder();
        }
        return start - layoutState.mAvailable;
    }
(6)LinearLayoutManager.layoutChunk
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        // 取出下一个View
        View view = layoutState.next(recycler);
        if (view == null) {
            if (DEBUG && layoutState.mScrapList == null) {
                throw new RuntimeException("received null view when unexpected");
            }
            // if we are laying out views in scrap, this may return null which means there is
            // no more items to layout.
            result.mFinished = true;
            return;
        }
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        // 测量子View(即每个item的预留空间)
        // 测量预留空间主要就是通过mItemDecorations遍历取出每个ItemDecoration,然后调用getItemOffsets
        // 在getItemOffsets中调用outRect给每个item预留空间,用于绘制
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        if (DEBUG) {
            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                    + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                    + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
        }
        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.hasFocusable();
    }
(7)LinearLayoutManager.LayoutState.next
        View next(RecyclerView.Recycler recycler) {
            if (mScrapList != null) {
                return nextViewFromScrapList();
            }
            // 调用RecyclerView.Recycler的复用逻辑,这里就是进行复用的流程
            // 这个View其实取出的就是ViewHolder.itemView
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            return view;
        }
(8)RecyclerView.Recycler.getViewForPosition

这个方法内部很简单,主要是做了一件事,其实就是调用RecyclerView.Recycler.tryGetViewHolderForPositionByDeadline方法,这个方法在预取的时候,其实就是用来复用或者创建ViewHolder的

(9)RecyclerView.Recycler.tryGetViewHolderForPositionByDeadline

tryGetViewHolderForPositionByDeadline方法是RecyclerView的整个预取复用流程的关键,因为RecyclerView的缓存其实是基于ViewHolder的,需要的View其实也是从ViewHolder中取出

        @Nullable
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            if (position < 0 || position >= mState.getItemCount()) {
                throw new IndexOutOfBoundsException("Invalid item position " + position
                        + "(" + position + "). Item count:" + mState.getItemCount()
                        + exceptionLabel());
            }
            boolean fromScrapOrHiddenOrCache = false;
            ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            // 判断mChangedScrap是否有缓存,如果有则取出
            // 不过这个有一个前提,就是在预布局的时候,我们这个流程是onTouchEvent的时候所以并不会满足
            if (mState.isPreLayout()) {
                // 分别根据position或者id取出对应的ViewHolder
                // ChangedScrap缓存主要是与动画相关的
                // 这里有一个条件mState.isPreLayout()要为true
                // 一般在我们调用adapter的notifyItemChanged等方法时为true
                // 因为数据发生了变化,viewholder被detach掉后缓存在mChangedScrap之中,在这里拿到的viewHolder后续需要重新绑定
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 1) Find by position from scrap/hidden list/cache
            // 从mAttachedScrap或者mCachedViews中查找是否有ViewHolder,这里是通过position寻找
            // 即判断ViewHolder.LayoutPosition==position
            if (holder == null) {
                // 主要是根据position获取
                // 这里可以做三件事:1.从mAttachedScrap一级缓存中找到ViewHolder
                // 2.回收隐藏的但是未删除的View到一级缓存中(调用scrapView,可能是进入mAttachedScrap也可能是mChangedScrap)
                // 3.从mCachedViews二级缓存中找到ViewHolder
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle holder (and unscrap if relevant) since it can't be used
                        // 回收该holder,因为该holder不可用,这时是将这个不可用的ViewHolder加入到二级缓存中
                        // 这个ViewHolder可能是从一级缓存中取出,也可能是从二级缓存中取出
                        if (!dryRun) {
                            // we would like to recycle this but need to make sure it is not used by
                            // animation logic etc.
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            if (holder.isScrap()) {
                                removeDetachedView(holder.itemView, false);
                                holder.unScrap();
                            } else if (holder.wasReturnedFromScrap()) {
                                holder.clearReturnedFromScrapFlag();
                            }
                            // 如果取出的ViewHolder是不可用的,则加入到mCachedViews中,
                            // 但是如果不满足加入到mCachedViews的条件,则会加入到RecyclerViewPool中
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrapOrHiddenOrCache = true;
                    }
                }
            }
            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                            + "position " + position + "(offset:" + offsetPosition + ")."
                            + "state:" + mState.getItemCount() + exceptionLabel());
                }

                final int type = mAdapter.getItemViewType(offsetPosition);
                // 2) Find from scrap/cache via stable ids, if exists
                // 找到ViewHolder对应的itemId,从mAttachedScrap或者mCachedViews中找到对应的ViewHolder
                // 即通过判断holder.getItemId() == id,id是传入的目标id
                if (mAdapter.hasStableIds()) {
                    // 首先会从mAttachedScrap获取ViewHolder
                    // 如果是不可用的,则会调用quickRecycleScrapView,其内部会调用recycleViewHolderInternal
                    // 将ViewHolder回收加入到mCachedViews中
                    // 如果mAttachedScrap找不到,则会从mCachedViews中查询ViewHolder
                    // 如果找到但是不可用的,则会调用recycleCachedViewAt,其内部会调用addViewHolderToRecycledViewPool
                    // 将ViewHolder加入到RecyclerViewPool中
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrapOrHiddenOrCache = true;
                    }
                }
                // 从mViewCacheExtension中查询缓存的ViewHolder
                if (holder == null && mViewCacheExtension != null) {
                    // We are NOT sending the offsetPosition because LayoutManager does not
                    // know it.
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        if (holder == null) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view which does not have a ViewHolder"
                                    + exceptionLabel());
                        } else if (holder.shouldIgnore()) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view that is ignored. You must call stopIgnoring before"
                                    + " returning this view." + exceptionLabel());
                        }
                    }
                }
                // 如果前面四种情况都没找到holder,则查询RecyclerViewPool
                // RecyclerViewPool缓存默认上限是5个,是每个ViewType的上限为5个
                // 根据ViewHolder的ViewType先查找对应的缓存ViewHolder的List
                // 然后从List的末尾出队一个ViewHolder
                if (holder == null) { // fallback to pool
                    if (DEBUG) {
                        Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                                + position + ") fetching from shared pool");
                    }
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                // 如果前面四步查询缓存都没找到对应的ViewHolder,则调用adapter.createViewHolder创建ViewHolder
                if (holder == null) {
                    long start = getNanoTime();
                    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);
                    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);
                        }
                    }

                    long end = getNanoTime();
                    mRecyclerPool.factorInCreateTime(type, end - start);
                    if (DEBUG) {
                        Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
                    }
                }
            }

            // This is very ugly but the only place we can grab this information
            // before the View is rebound and returned to the LayoutManager for post layout ops.
            // We don't need this in pre-layout since the VH is not updated by the LM.
            if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
                    .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
                holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                if (mState.mRunSimpleAnimations) {
                    int changeFlags = ItemAnimator
                            .buildAdapterChangeFlagsForAnimations(holder);
                    changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                            holder, changeFlags, holder.getUnmodifiedPayloads());
                    recordAnimationInfoIfBouncedHiddenView(holder, info);
                }
            }

            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // do not update unless we absolutely have to.
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                if (DEBUG && holder.isRemoved()) {
                    throw new IllegalStateException("Removed holder should be bound and it should"
                            + " come here only in pre-layout. Holder: " + holder
                            + exceptionLabel());
                }
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                // 获取到holder之后,调用Adapter.bindViewHolder方法绑定ViewHolder
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }

            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
            if (lp == null) {
                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;
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
            return holder;
        }
tryGetViewHolderForPositionByDeadline方法总结:

在复用阶段:

  • 首先会从getScrapOrHiddenOrCachedHolderForPosition查询,如果找到的ViewHolder是不可用的,如果是从一级缓存中找到的,则加入到mCachedViews中,如果是从二级缓存中找到的,则加入到mRecyclerViewPool中。在这里,如果dryRun为false的时候,还会去获取隐藏的但是没有被remove的View,调用scrapView方法保存在mAttachedScrap或者mChangedScrap
  • 如果Adapter有稳定的id,则从getScrapOrCachedViewForId中获取ViewHolder,首先也是从一级缓存mAttachedScrap中获取,如果该holder是不可用的,则调用quickRecycleScrapView,其内部调用recycleViewHolderInternal,将ViewHolder加入到二级缓存中,但是如果mCachedViews已经无法加入,则会将mCachedViews的第0个出队加入到RecyclerViewPool中,然后将新进入的ViewHolder加入到mCachedViews中,如果一级缓存中无法获取到对应的ViewHolder,则从二级缓存mCachedViews中获取,如果获取到的ViewHolder是不可用的,则通过调用recycleCachedViewAt方法,在其内部调用addViewHolderToRecycledViewPool方法,将ViewHolder加入到RecyclerViewPool中。
tryGetViewHolderForPositionByDeadline
->getScrapOrHiddenOrCachedHolderForPosition
->scrapView(回收隐藏但没有remove的View加入一级缓存)
->recycleViewHolderInternal(回收取自一级缓存的ViewHolder加入mCachedViews,回收取自mCachedViews中的ViewHolder加入RecyclerViewPool)
tryGetViewHolderForPositionByDeadline
->getScrapOrCachedViewForId
  ->quickRecycleScrapView(回收取自mAttachedScrap一级缓存的View)
    ->recycleViewHolderInternal(具体回收,将上一步取出的View加入mCachedViews)
  ->recycleCachedViewAt(回收取自mCachedViews二级缓存的View)
    ->addViewHolderToRecycledViewPool(将上一步取自mCachedViews的View,加入到RecyclerViewPool中)

整个复用流程,其实最终是通过调用RecyclerView.Recycler.tryGetViewHolderForPositionByDeadline方法进行,而复用流程中,还会判断从缓存中或者隐藏但是没有remove的ViewHolder是否可以使用,如果不可使用,就从低级缓存加入到高级缓存中。
在这里使用的方法,其实都是属于RecyclerView.Recycler这个内部类,Recycler是真正对ViewHolder进行回收复用的类

关于RecyclerViewPool

Pool默认大小为5个。即每个ViewType对应的ArrayList的大小最大为5个。
RecyclerViewPool是通过ViewType进行缓存的,缓存的是一个ArrayList<VIewHolder>
RecyclerViewPool.ScrapData就是RecyclerViewPool的缓存单元

        static class ScrapData {
            final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
            int mMaxScrap = DEFAULT_MAX_SCRAP;
            long mCreateRunningAverageNs = 0;
            long mBindRunningAverageNs = 0;
        }

RecyclerViewPool,是根据ViewType取出对应的缓存数据。根据ViewType缓存ScrapData到SparseArray中,而ScrapData这个类中有一个ArrayList,这个ArrayList就是用来缓存这个ViewType类型对应的ViewHolder的。这里是一个先进后出的结构,这是跟回收机制有关,才采用先进后出的结构。看下面的源码,可以看出,每次对RecyclerViewPool的出队都是优先最后一个,入队都是添加到末尾。

        @Nullable
        public ViewHolder getRecycledView(int viewType) {
            final ScrapData scrapData = mScrap.get(viewType);
            if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
                final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
                return scrapHeap.remove(scrapHeap.size() - 1);
            }
            return null;
        }
(10)RecyclerView的预布局

判断RecyclerView是否处于预布局,则需要通过RecyclerView.State.mInPreLayout属性来判断,即当mInPreLayout为true的时候是处于预布局的时候。
而mInPreLayout赋值为true的地方有两部分,一部分是在onMeasure中,一部分是在onLayout中。不过这两部分都与RecyclerView.State.mRunPredictiveAnimations挂钩,在onMeasure中的mInPreLayout = true,需要满足RecyclerView.State.mRunPredictiveAnimations = true,而在onLayout中,直接就是将mInPreLayout = mRunPredictiveAnimations,所以就需要找到mRunPredictiveAnimations赋值为true的地方,即在RecyclerView.processAdapterUpdatesAndSetAnimationFlags()方法中,将mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations,所以有需要找到mRunSimpleAnimations赋值为true的地方,而mRunSimpleAnimations的赋值又与另一个变量有关,即RecyclerView.mFirstLayoutComplete,而mFirstLayoutComplete赋值为true的时候,才能让mInPreLayout为true。
mFirstLayoutComplete赋值为true,是在onLayout中,但是是在onLayout的末尾才赋值为true。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
    }

所以在第一次执行onLayout的时候,肯定不会执行预布局。其实onLayout调用过程中,调用dispatchLayout中dispatchLayoutStep1()方法其实主要就是用来完成预布局功能。
dispatchLayoutStep1()
1.处理Adapter更新;2.决定是否执行ItemAnimator;3.保存ItemView的动画信息。本方法也被称为preLayout(预布局),当Adapter更新了,这个方法会保存每个ItemView的旧信息(oldViewHolderInfo)

2.RecyclerView.onLayout触发回收复用

(1)RecyclerView.onLayout
(2)RecyclerView.dispatchLayout()
(3)RecyclerView.dispatchLayoutStep1/2/3

不过dispatchLayoutStep1()内部的执行,都想需要依赖于mRunSimpleAnimations为true,而mRunSimpleAnimations为true,需要mFirstLayoutComplete为true,所以在第一次执行onLayout的时候,并不会执行dispatchLayoutStep1()中的主要的内容。

    // 这里如果mRunSimpleAnimations不为true,主要就是做一些初始化
    private void dispatchLayoutStep1() {
        mState.assertLayoutStep(State.STEP_START);
        // 保存剩余的滚动值,包括x和y方向还可以滚动多少距离
        fillRemainingScrollValues(mState);
        mState.mIsMeasuring = false;
        // 开始拦截布局请求,应该是拦截requestLayout过程
        startInterceptRequestLayout();
        mViewInfoStore.clear();
        // 布局或者滚动计数+1,即onTouchEvent的move事件或者onLayout过程的计数+1
        onEnterLayoutOrScroll();
        processAdapterUpdatesAndSetAnimationFlags();
        saveFocusInfo();
        mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
        mItemsAddedOrRemoved = mItemsChanged = false;
        mState.mInPreLayout = mState.mRunPredictiveAnimations;
        mState.mItemCount = mAdapter.getItemCount();
        findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
        // 运行简单动画
        if (mState.mRunSimpleAnimations) {
            // Step 0: Find out where all non-removed items are, pre-layout
            // 找到没有被remove的itemView,并且将这个itemView的ViewHolder保存在mViewInfoStore,
            // 同时还将预布局的位置也保存在mViewInfoStore中
            int count = mChildHelper.getChildCount();
            for (int i = 0; i < count; ++i) {
                final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
                if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
                    continue;
                }
                final ItemHolderInfo animationInfo = mItemAnimator
                        .recordPreLayoutInformation(mState, holder,
                                ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                                holder.getUnmodifiedPayloads());
                mViewInfoStore.addToPreLayout(holder, animationInfo);
                if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
                        && !holder.shouldIgnore() && !holder.isInvalid()) {
                    long key = getChangedHolderKey(holder);
                    // This is NOT the only place where a ViewHolder is added to old change holders
                    // list. There is another case where:
                    //    * A VH is currently hidden but not deleted
                    //    * The hidden item is changed in the adapter
                    //    * Layout manager decides to layout the item in the pre-Layout pass (step1)
                    // When this case is detected, RV will un-hide that view and add to the old
                    // change holders list.
                    mViewInfoStore.addToOldChangeHolders(key, holder);
                }
            }
        }
        // 运行预动画
        if (mState.mRunPredictiveAnimations) {
            // 在这里会使用旧的position的item进行预布局。而且在这里会调用onLayoutChildren进行布局
            // 不过这里只是进行预布局,只是先确定每个itemView的位置,预布局之后,
            // 此时取到的每个ItemView的ViewHolder和ItemHolderInfo,便是每个ItemView的最终信息。
            // Step 1: run prelayout: This will use the old positions of items. The layout manager
            // is expected to layout everything, even removed items (though not to add removed
            // items back to the container). This gives the pre-layout position of APPEARING views
            // which come into existence as part of the real layout.

            // Save old positions so that LayoutManager can run its mapping logic.
            saveOldPositions();
            final boolean didStructureChange = mState.mStructureChanged;
            mState.mStructureChanged = false;
            // temporarily disable flag because we are asking for previous layout
            mLayout.onLayoutChildren(mRecycler, mState);
            mState.mStructureChanged = didStructureChange;

            for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
                final View child = mChildHelper.getChildAt(i);
                final ViewHolder viewHolder = getChildViewHolderInt(child);
                if (viewHolder.shouldIgnore()) {
                    continue;
                }
                if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                    int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
                    boolean wasHidden = viewHolder
                            .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    if (!wasHidden) {
                        flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    }
                    final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                            mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                    if (wasHidden) {
                        recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                    } else {
                        mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                    }
                }
            }
            // we don't process disappearing list because they may re-appear in post layout pass.
            clearOldPositions();
        } else {
            clearOldPositions();
        }
        onExitLayoutOrScroll();
        stopInterceptRequestLayout(false);
        mState.mLayoutStep = State.STEP_LAYOUT;
    }
(4)LinearLayoutManager.onLayoutChildren()

从这个方法开始,其实流程就与MOVE事件滑动触发onTouchEvent,然后复用或者创建ViewHolder的流程一致。

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // layout algorithm:
        // 1) by checking children and other variables, find an anchor coordinate and an anchor
        //  item position.
        // 2) fill towards start, stacking from bottom
        // 3) fill towards end, stacking from top
        // 4) scroll to fulfill requirements like stack from bottom.
        // create layout state
        if (DEBUG) {
            Log.d(TAG, "is pre layout:" + state.isPreLayout());
        }
        if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
            // 如果列表的item数目为0,清空所有的View
            if (state.getItemCount() == 0) {
                removeAndRecycleAllViews(recycler);
                return;
            }
        }
        if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
            mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
        }

        ensureLayoutState();
        mLayoutState.mRecycle = false;
        // resolve layout direction
        resolveShouldLayoutReverse();
        // 第一步:确定锚点信息
        final View focused = getFocusedChild();
        if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
                || mPendingSavedState != null) {
            mAnchorInfo.reset();
            mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
            // 计算锚点的位置和坐标
            updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
            mAnchorInfo.mValid = true;
        } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                        >= mOrientationHelper.getEndAfterPadding()
                || mOrientationHelper.getDecoratedEnd(focused)
                <= mOrientationHelper.getStartAfterPadding())) {
            mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
        }
        if (DEBUG) {
            Log.d(TAG, "Anchor info:" + mAnchorInfo);
        }

        // LLM may decide to layout items for "extra" pixels to account for scrolling target,
        // caching or predictive animations.
        int extraForStart;
        int extraForEnd;
        final int extra = getExtraLayoutSpace(state);
        // If the previous scroll delta was less than zero, the extra space should be laid out
        // at the start. Otherwise, it should be at the end.
        if (mLayoutState.mLastScrollDelta >= 0) {
            extraForEnd = extra;
            extraForStart = 0;
        } else {
            extraForStart = extra;
            extraForEnd = 0;
        }
        extraForStart += mOrientationHelper.getStartAfterPadding();
        extraForEnd += mOrientationHelper.getEndPadding();
        if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION
                && mPendingScrollPositionOffset != INVALID_OFFSET) {
            // if the child is visible and we are going to move it around, we should layout
            // extra items in the opposite direction to make sure new items animate nicely
            // instead of just fading in
            final View existing = findViewByPosition(mPendingScrollPosition);
            if (existing != null) {
                final int current;
                final int upcomingOffset;
                if (mShouldReverseLayout) {
                    current = mOrientationHelper.getEndAfterPadding()
                            - mOrientationHelper.getDecoratedEnd(existing);
                    upcomingOffset = current - mPendingScrollPositionOffset;
                } else {
                    current = mOrientationHelper.getDecoratedStart(existing)
                            - mOrientationHelper.getStartAfterPadding();
                    upcomingOffset = mPendingScrollPositionOffset - current;
                }
                if (upcomingOffset > 0) {
                    extraForStart += upcomingOffset;
                } else {
                    extraForEnd -= upcomingOffset;
                }
            }
        }
        int startOffset;
        int endOffset;
        final int firstLayoutDirection;
        if (mAnchorInfo.mLayoutFromEnd) {
            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
                    : LayoutState.ITEM_DIRECTION_HEAD;
        } else {
            firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
                    : LayoutState.ITEM_DIRECTION_TAIL;
        }

        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
        // 对所有的ItemView进行回收,这里是将ItemView回收到了mCachedViews中
        detachAndScrapAttachedViews(recycler);
        mLayoutState.mInfinite = resolveIsInfinite();
        mLayoutState.mIsPreLayout = state.isPreLayout();
        if (mAnchorInfo.mLayoutFromEnd) {
            // 开始填充
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
            final int firstElement = mLayoutState.mCurrentPosition;
            if (mLayoutState.mAvailable > 0) {
                extraForEnd += mLayoutState.mAvailable;
            }
            // fill towards end
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;

            if (mLayoutState.mAvailable > 0) {
                // end could not consume all. add more items towards start
                extraForStart = mLayoutState.mAvailable;
                updateLayoutStateToFillStart(firstElement, startOffset);
                mLayoutState.mExtra = extraForStart;
                fill(recycler, mLayoutState, state, false);
                startOffset = mLayoutState.mOffset;
            }
        } else {
            // fill towards end
            updateLayoutStateToFillEnd(mAnchorInfo);
            mLayoutState.mExtra = extraForEnd;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
            final int lastElement = mLayoutState.mCurrentPosition;
            if (mLayoutState.mAvailable > 0) {
                extraForStart += mLayoutState.mAvailable;
            }
            // fill towards start
            updateLayoutStateToFillStart(mAnchorInfo);
            mLayoutState.mExtra = extraForStart;
            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;

            if (mLayoutState.mAvailable > 0) {
                extraForEnd = mLayoutState.mAvailable;
                // start could not consume all it should. add more items towards end
                updateLayoutStateToFillEnd(lastElement, endOffset);
                mLayoutState.mExtra = extraForEnd;
                fill(recycler, mLayoutState, state, false);
                endOffset = mLayoutState.mOffset;
            }
        }

        // changes may cause gaps on the UI, try to fix them.
        // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
        // changed
        if (getChildCount() > 0) {
            // because layout from end may be changed by scroll to position
            // we re-calculate it.
            // find which side we should check for gaps.
            if (mShouldReverseLayout ^ mStackFromEnd) {
                int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
                startOffset += fixOffset;
                endOffset += fixOffset;
                fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
                startOffset += fixOffset;
                endOffset += fixOffset;
            } else {
                int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
                startOffset += fixOffset;
                endOffset += fixOffset;
                fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
                startOffset += fixOffset;
                endOffset += fixOffset;
            }
        }
        layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
        if (!state.isPreLayout()) {
            mOrientationHelper.onLayoutComplete();
        } else {
            mAnchorInfo.reset();
        }
        mLastStackFromEnd = mStackFromEnd;
        if (DEBUG) {
            validateChildOrder();
        }
    }

3.RecyclerView缓存复用机制总结

其实复用机制,最终都是进入fill方法进行复用,将ViewHolder从缓存中取出进行复用。而fill获取View,然后将View通过addView添加到布局中。而fill()方法中获取的View,其实就是通过调用LayoutState.next(),内部会优先从4级缓存中取出holder,如果都没有,则调用createViewHolder创建holder,然后会返回holder中的itemView用来添加到RecyclerView中。

四、RecyclerView的ViewHolder的回收

在onLayout流程的回收中,其实会有两部分缓存的回收,其一就是在onLayoutChildren中调用detachAndScrapAttachedViews调用scrapOrRecycleView()回收holder到一级缓存,调用RecyclerView.Recycler.recycleViewHolderInternal回收holder到2-4级缓存中;其二就是在onLayoutChildren中调用fill回收holder到2-4级缓存中。


RecyclerView回收机制流程.png

RecyclerView的ViewHolder的回收,也从onTouchEvent和onLayout两个流程进行分析

1.onTouchEvent分析ViewHolder的回收

前面一部分与ViewHolder复用过程其实是一样的。
RecyclerView.onTouchEvent的move事件->
RecyclerView.scrollByInternal->
mLayout.scrollVerticallyBy()(mLayout其实就是LayoutManager)->
LinearLayoutManager.scrollVerticallyBy()->
LinearLayoutManager.scrollBy()->
LinearLayoutManager.fill(recycler, mLayoutState, state, false)->
即最终都会执行LinearLayoutManager.fill()方法,而在LinearLayoutManager.fill()方法中,首先就会调用recycleByLayoutState进行回收。

(1)LinearLayoutManager.recycleByLayoutState
    private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (!layoutState.mRecycle || layoutState.mInfinite) {
            return;
        }
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
        } else {
            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
        }
    }

这里的两个方法其实类似,只不过一个是开始位置,一个是从结束位置。

(2)LinearLayoutManager.recycleViewsFromEnd
    private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
        final int childCount = getChildCount();
        if (dt < 0) {
            if (DEBUG) {
                Log.d(TAG, "Called recycle from end with a negative value. This might happen"
                        + " during layout changes but may be sign of a bug");
            }
            return;
        }
        final int limit = mOrientationHelper.getEnd() - dt;
        if (mShouldReverseLayout) {
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedStart(child) < limit
                        || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
                    // stop here
                    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) {
                    // stop here
                    recycleChildren(recycler, childCount - 1, i);
                    return;
                }
            }
        }
    }

在这里其实就是调用recycleChildren进行回收

(3)LinearLayoutManager.recycleChildren
    private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
        if (startIndex == endIndex) {
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
        }
        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);
            }
        }
    }
(4)LinearLayoutManager.removeAndRecycleViewAt
        public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
            final View view = getChildAt(index);
            // 在这里,只是先remove,但是并没有detach
            // 绑定的数据等信息都还在,这意味着从mCachedViews取出的视图如果符合需要的目标视图是可以直接展示的,而不需要重新绑定
            removeViewAt(index);
            recycler.recycleView(view);
        }

其实从这四步可以看出,最终就是调用RecyclerView.Recycler.recycleView()方法,而该方法内部就是通过调用RecyclerView.Recycler.recycleViewHolderInternal()进行回收,将ViewHolder保存在mCachedViews、RecyclerViewPool等二到四级缓存中。
在RecyclerView.Recycler.recycleViewHolderInternal()方法进行回收的过程中,首先会判断mCachedViews是否已经满了,mCachedViews的默认大小为2,如果mCachedViews已经满了,则会将先进入mCachedViews中的ViewHolder移除,并且将这个移除的ViewHolder加入到RecyclerViewPool中,在加入到RecyclerViewPool中的时候,会判断当前ViewType对应的List集合是否已经大于等于5个,如果已经达到这个最大值,则会放弃最新加入的ViewHolder。
mCachedViews中缓存的ViewHolder是带有数据的,所以这些ViewHolder是不同的;而RecyclerViewPool中缓存的ViewHolder是一样的,是不带有数据的。因为在将ViewHolder添加到pool之前,会调用ViewHolder.resetInternal方法重置数据,这样每个ViewHolder就变成一样的不带数据的。
因为在添加进mCachedViews之前,只是remove了视图,而没有detach,说明绑定的数据等信息都还在,这意味着从mCachedViews取出的视图如果符合需要的目标视图是可以直接展示的,而不需要重新绑定

2.onLayout回收流程

(1)根据onLayout的回收流程:在RecyclerView.dispatchLayout()中调用的方法解析

dispatchLayoutStep1

  1. Adapter的更新;
  2. 决定该启动哪种动画; 即是否执行ItemAnimator
  3. 保存当前View的信息(getLeft(), getRight(), getTop(), getBottom()等);
  4. 如果有必要,先跑一次布局并将信息保存下来。
    dispatchLayoutStep1方法其实也是RecyclerView的预布局,在第一次执行onLayout的时候,并不会执行该方法,因为该方法的执行在流程上mFirstLayoutComplete赋值为true的时候,而第一个onLayout的时候,dispatchLayoutStep1是在dispatchLayout()中执行,dispatchLayout()是早于mFirstLayoutComplete = true;,dispatchLayoutStep1预布局会保存每个ItemView的旧信息(oldViewHolderInfo)

dispatchLayoutStep2
真正对子View做布局的地方。

  1. 计算锚点,以锚点开始填充RecyclerView(其实就是执行fill方法)。
  2. 执行fill方法,判断RecyclerView是否还有空间,如果有,执行layoutChunk方法,直至填充满。
  3. layoutChunk方法中,寻找到当前要添加的子view,add到RecyclerView中。
  4. 对子view进行measure和layout。

dispatchLayoutStep3
为动画保存View的相关信息; 触发动画; 相应的清理工作。
其实dispatchLayoutStep3()就是做了一些收尾工作,将一些变量重置,处理下动画。

mState.mLayoutStep

  • 初始化为STEP_START
  • 执行完dispatchLayoutStep1后,mState.mLayoutStep = State.STEP_LAYOUT;
  • 执行完dispatchLayoutStep2后,mState.mLayoutStep = State.STEP_ANIMATIONS;
  • 执行完dispatchLayoutStep3后,mState.mLayoutStep = State.STEP_START;
(2)具体的onLayout回收流程

mAttachedScrap和mChangedScrap的值是通过onLayout流程中放入缓存的ViewHolder的。这个放入的流程
RecyclerView.onLayout->
RecyclerView.dispatchLayout()->
RecyclerView.dispatchLayoutStep1/2/3这三个方法->
LinearLayoutManager.onLayoutChildren()->
RecyclerView.LayoutManager.detachAndScrapAttachedViews->
RecyclerView.LayoutManager.scrapOrRecycleView()->
recycler.scrapView(view);
在detachAndScrapAttachedViews方法回收中,还有一种可能会调用RecyclerView.Recycler.recycleViewHolderInternal回收holder到2-4级缓存中。

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.shouldIgnore()) {
        if (DEBUG) {
            Log.d(TAG, "ignoring view " + viewHolder);
        }
        return;
    }
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        removeViewAt(index);
        // 回收到2-4级缓存中
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        detachViewAt(index);
        // 回收到一级缓存中。
        // 标记没有移除或者失效、ViewHolder没有更新,可重复使用更新ViewHolder的时候
        // 加入到mAttachedScrap中
        recycler.scrapView(view);
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}

如果这里的if判断,ViewHolder是无效的&&没有移除&&没有改变&&没有StateIds的话,则会调用RecyclerView.Recycler.recycleViewHolderInternal回收ViewHolder到2-4级缓存中。
这里就是一级的回收,将回收后的数据保存在mChangedScrap或者mAttachedScrap中。
但是并不是所有情况都会进行一级回收,只有当ViewHolder是无效的,并且没有被remove,并且adapter没有稳定的id的时候,就不会进行一级回收,而是与onTouchEvent中的回收流程一致,进行删除表项,将mCachedViews中的缓存添加到Pool中,然后向mCachedViews中添加新的缓存。
而一级缓存只有在onLayout布局阶段才会回收然后进行添加,并且并不是所有的布局阶段都会添加一级缓存。

        void recycleViewHolderInternal(ViewHolder holder) {
            if (holder.isScrap() || holder.itemView.getParent() != null) {
                throw new IllegalArgumentException(
                        "Scrapped or attached views may not be recycled. isScrap:"
                                + holder.isScrap() + " isAttached:"
                                + (holder.itemView.getParent() != null) + exceptionLabel());
            }

            if (holder.isTmpDetached()) {
                throw new IllegalArgumentException("Tmp detached view should be removed "
                        + "from RecyclerView before it can be recycled: " + holder
                        + exceptionLabel());
            }

            if (holder.shouldIgnore()) {
                throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
                        + " should first call stopIgnoringView(view) before calling recycle."
                        + exceptionLabel());
            }
            //noinspection unchecked
            final boolean transientStatePreventsRecycling = holder
                    .doesTransientStatePreventRecycling();
            final boolean forceRecycle = mAdapter != null
                    && transientStatePreventsRecycling
                    && mAdapter.onFailedToRecycleView(holder);
            boolean cached = false;
            boolean recycled = false;
            if (DEBUG && mCachedViews.contains(holder)) {
                throw new IllegalArgumentException("cached view received recycle internal? "
                        + holder + exceptionLabel());
            }
            if (forceRecycle || holder.isRecyclable()) {
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        // 当mCachedViews存放满了,则会mCachedViews的第0个加入到RecyclerViewPool中,并且删除第0个
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }

                    int targetCacheIndex = cachedViewSize;
                    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.add(targetCacheIndex, holder);
                    cached = true;
                }
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } else {
                // NOTE: A view can fail to be recycled when it is scrolled off while an animation
                // runs. In this case, the item is eventually recycled by
                // ItemAnimatorRestoreListener#onAnimationFinished.

                // TODO: consider cancelling an animation when an item is removed scrollBy,
                // to return it to the pool faster
                if (DEBUG) {
                    Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                            + "re-visit here. We are still removing it from animation lists"
                            + exceptionLabel());
                }
            }
            // even if the holder is not removed, we still call this method so that it is removed
            // from view holder lists.
            mViewInfoStore.removeViewHolder(holder);
            if (!cached && !recycled && transientStatePreventsRecycling) {
                holder.mOwnerRecyclerView = null;
            }
        }
四级缓存数据回收总结
  • 一级回收都是调用的Recycler.scrapView,这个方法可能是在onLayout中被调用,也可能是在复用的时候回收隐藏但是没有被remove的ViewHolder。在onLayout的过程中,是在调用detachAndScrapAttachedViews方法中,调用scrapOrRecycleView中的Recycler.scrapView进行一级缓存的回收,可能是mChangedScrap,也可能是mAttachedScrap;而在复用阶段回收,即在fill填充的时候回收到一级缓存中,则是在tryGetViewHolderForPositionByDeadline方法根据position获取ViewHolder的时候,调用getScrapOrHiddenOrCachedHolderForPosition方法中,判断ViewHolder是隐藏且没有被remove的,调用Recycler.scrapView进行一级缓存的回收,加入到mChangedScrap或者mAttachedScrap

  • 二级回收都是调用的Recycler.recycleViewHolderInternal方法,这个方法可能是在下面的情况中被调用

RecyclerView.removeAnimatingView
在onLayout的回收流程中LinearLayoutManager.scrapOrRecycleView
在复用流程中tryGetViewHolderForPositionByDeadline中根据position获取的ViewHolder不可用的时候
tryGetViewHolderForPositionByDeadline --> getScrapOrCachedViewForId -->quickRecycleScrapView
预加载prefetchPositionWithDeadline
removeView的时候

recycleViewHolderInternal方法主要作用是回收ViewHolder到mCachedViews中,如果不能加入到mCachedViews中,则会将mCachedViews中的第一个加入到RecyclerViewPool中,然后将mCachedViews中的第一个出队,再将新加入的ViewHolder加入到mCachedViews中

  • 四级回收RecyclerViewPool都是调用的addViewHolderToRecycledViewPool,该方法会在回收阶段和复用阶段被调用。
    在复用阶段,即在tryGetViewHolderForPositionByDeadline中因为获取到的ViewHolder不可用,则可能会加入到ReyclcerViewPool中,一种是通过position获取ViewHolder的时候,不可用则直接调用Recycler.recycleViewHolderInternal,一种是通过ID获取ViewHolder,即getScrapOrCachedViewForId方法中,从mCachedViews中获取的ViewHolder是不可用的时候;
    在回收阶段,四级缓存的回收,最终都是调用Recycler.recycleViewHolderInternal回收四级缓存,

参考:

在自己写的时候,有参考这个作者写的一些关于RecyclerView的文章,这里就写出其中一篇作为入口
其实RecyclerView的布局流程、滑动机制、缓存机制贯穿整个RecyclerView。
RecyclerView 源码分析(一) - RecyclerView的三大流程
https://www.jianshu.com/p/61fe3f3bb7ec
https://phantomvk.github.io/2019/02/13/RecyclerView_cache/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270