目錄
RecyclerView預加載機制分析
相關鏈接
- RecyclerView預加載實測:https://blog.csdn.net/crazy_everyday_xrp/article/details/70344638
- 這篇文章最全面:https://medium.com/google-developers/recyclerview-prefetch-c2f269075710 (看原理得看這個)
預加載原理簡介
相關鏈接
原理分析
- 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);
}