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); }复制代码
相关链接
- RecyclerView预加载实测:
- 这篇文章最全面: (看原理得看这个)