RecyclerView預加載機制源碼分析

目錄

RecyclerView預加載機制分析

相關鏈接

預加載原理簡介

相關鏈接

原理分析

  • SDK>=21,支持RenderThread
  • UI Thread:將UI(xml,輸入,動畫)轉化爲FrameBuffer
  • RenderThread:將FrameBuffer轉化爲顯示器顯示的指令
  • 核心思想:將閒置的UI Thead利用起來,提前加載計算下一幀的FrameBuffer

預加載流程總覽

  • RecyclerView
    • dragging的時候提交預加載任務
  • GapWorkder
    • 執行預加載任務,運行在主線程
  • LayoutPrefetchRegistryImpl
    • 根據touch的位置計算預加載的數量和position
  • Recycler
    • 創建ViewHolder

  • 問題
    • 什麼時候開始預加載
    • 預加載幾個,哪幾個
    • 預加載超時怎麼計算

預加載源碼分析

RecyclerView.onTouchEvent

  • 在View拖動的時候通過mGapWorker.postFromTraversal,提交預加載的任務

    @Override
    public boolean onTouchEvent(MotionEvent e) {
            case MotionEvent.ACTION_MOVE: {

                if (mScrollState != SCROLL_STATE_DRAGGING) {

                    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
            if (mPostTimeNs == 0) {
                mPostTimeNs = recyclerView.getNanoTime();
                recyclerView.post(this);
            }
        }
        // 後續的動作觸發去更新最新的dx和dy,prefect會按照這個最新的dx和dy計算prefetch的item的position
        recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
    }

GapWorker.run

@Override
    public void run() {
        try {
            TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);
            // mRecyclerViews的原因是存在嵌套的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);
    }
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) {
         // 計算每一個RecyclerView預加載的數量,保存在LayoutPrefetchRegistryImpl.count裏面
                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);
            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,那麼次次預加載就失敗了
        RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
                task.position, taskDeadlineNs);
        if (holder != null
                && holder.mNestedRecyclerView != null
                && holder.isBound()
                && !holder.isInvalid()) {
            prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
        }
    }

RecyclerView.tryGetViewHolderForPositionByDeadline

  • 根據dx和dy,以及當前滑動的方案計算預加載的position
  • dx和dy是通過Gapworker.postFromTraversal在滑動的時候來更新的
// 如果不能在預測的預加載時間內預加載出來,那麼就不會去預加載
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;
                    }

RecyclerView.willCreateInTime

  • 預測的時間=當前開始執行時間+歷史該Type的ViewHolder創建的平均時間
boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) {
            long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs;
            return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
        }

預加載哪個和具體的位置是怎麼計算的

collectPrefetchPositionsFromView的調用流程圖

  • 在buildTaskList的時候根據LayoutPrefetchRegistryImpl記錄的滑動的位置,計算預加載的數量和位置

LayoutPrefetchRegistryImpl.collectPrefetchPositionsFromView

// nested:是否嵌套的,執行RecyclerView裏面嵌套的RecyclerView的預加載
void collectPrefetchPositionsFromView(RecyclerView view, boolean nested) {
            mCount = 0;
            if (mPrefetchArray != null) {
                Arrays.fill(mPrefetchArray, -1);
            }

            final RecyclerView.LayoutManager layout = view.mLayout;
            if (view.mAdapter != null
                    && layout != null
                    && layout.isItemPrefetchEnabled()) {
                if (nested) {
                    // nested prefetch, only if no adapter updates pending. Note: we don't query
                    // view.hasPendingAdapterUpdates(), as first layout may not have occurred
                    if (!view.mAdapterHelper.hasPendingUpdates()) {
                        layout.collectInitialPrefetchPositions(view.mAdapter.getItemCount(), this);
                    }
                } else {
                    // momentum based prefetch, only if we trust current child/adapter state
                    if (!view.hasPendingAdapterUpdates()) {
                        layout.collectAdjacentPrefetchPositions(mPrefetchDx, mPrefetchDy,
                                view.mState, this);
                    }
                }

                if (mCount > layout.mPrefetchMaxCountObserved) {
                    layout.mPrefetchMaxCountObserved = mCount;
                    layout.mPrefetchMaxObservedInInitialPrefetch = nested;
                    view.mRecycler.updateViewCacheSize();
                }
            }
        }

LinearLyoutManager.collectInitialPrefetchPositions

  • 計算預加載的位置
@Override
    public void collectInitialPrefetchPositions(int adapterItemCount,
            LayoutPrefetchRegistry layoutPrefetchRegistry) {
        final boolean fromEnd;
        final int anchorPos;
        if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
            // use restored state, since it hasn't been resolved yet
            fromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
            anchorPos = mPendingSavedState.mAnchorPosition;
        } else {
            resolveShouldLayoutReverse();
            fromEnd = mShouldReverseLayout;
            if (mPendingScrollPosition == NO_POSITION) {
                anchorPos = fromEnd ? adapterItemCount - 1 : 0;
            } else {
                anchorPos = mPendingScrollPosition;
            }
        }

        final int direction = fromEnd
                ? LayoutState.ITEM_DIRECTION_HEAD
                : LayoutState.ITEM_DIRECTION_TAIL;
                // 計算目標預加載位置=當前方向最後一個位置+方向
        int targetPos = anchorPos;
        for (int i = 0; i < mInitialPrefetchItemCount; i++) {
            if (targetPos >= 0 && targetPos < adapterItemCount) {
                layoutPrefetchRegistry.addPosition(targetPos, 0);
            } else {
                break; // no more to prefetch
            }
            targetPos += direction;
        }
    }

LayoutPrefetchRegistryImpl.addPosition

  • 保存預先加載的位置到mPrefetchArray之中
@Override
        public void addPosition(int layoutPosition, int pixelDistance) {
            if (layoutPosition < 0) {
                throw new IllegalArgumentException("Layout positions must be non-negative");
            }

            if (pixelDistance < 0) {
                throw new IllegalArgumentException("Pixel distance must be non-negative");
            }

            // allocate or expand array as needed, doubling when needed
            final int storagePosition = mCount * 2;
            if (mPrefetchArray == null) {
                mPrefetchArray = new int[4];
                Arrays.fill(mPrefetchArray, -1);
            } else if (storagePosition >= mPrefetchArray.length) {
                final int[] oldArray = mPrefetchArray;
                mPrefetchArray = new int[storagePosition * 2];
                System.arraycopy(oldArray, 0, mPrefetchArray, 0, oldArray.length);
            }

            // add position
            // 數組元素2個爲一組,第一保存預加載位置,第二個保存pixelDistance
            mPrefetchArray[storagePosition] = layoutPosition;
            mPrefetchArray[storagePosition + 1] = pixelDistance;

            mCount++;
        }

超時怎麼計算

GapWorker.run

  • 繪製的最遲時間是下一次同步信號到來的時間
@Override
    public void run() {

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

Recycler.tryGetViewHolderForPositionByDeadline

if (deadlineNs != FOREVER_NS
                            && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
                        // abort - we have a deadline we can't meet
                        return null;
                    }

RecyclerView.willCreateInTime

  • 如果預測的繪製時間晚於超時時間,那麼預加載失敗,直接返回
  • 預測的繪製時間=當前時間+該Type的ViewHolder歷史繪製的平均時間
boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) {
            long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs;
            return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章