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的三大流程
http://www.lxweimin.com/p/61fe3f3bb7ec
https://phantomvk.github.io/2019/02/13/RecyclerView_cache/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370