说起预加载,其实之前面试的时候被问到的,然后最近看到一篇关于预加载的文章,然后颇有感受,因此才有该篇文章。
在recyclerview的onAttachedToWindow有这么一句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
/**
* On L+, with RenderThread, the UI thread has idle time after it has passed a frame off to
* RenderThread but before the next frame begins. We schedule prefetch work in this window.
*/
static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21;
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
//省略代码
if (ALLOW_THREAD_GAP_WORK) {
// Register with gap worker
mGapWorker = GapWorker.sGapWorker.get();
if (mGapWorker == null) {
mGapWorker = new GapWorker();
// break 60 fps assumption if data from display appears valid
// NOTE: we only do this query once, statically, because it's very expensive (> 1ms)
Display display = ViewCompat.getDisplay(this);
float refreshRate = 60.0f;
if (!isInEditMode() && display != null) {
float displayRefreshRate = display.getRefreshRate();
if (displayRefreshRate >= 30.0f) {
refreshRate = displayRefreshRate;
}
}
mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate);
GapWorker.sGapWorker.set(mGapWorker);
}
mGapWorker.add(this);
}
}
|
可以看到有一个静态变量,ALLOW_THREAD_GAP_WORK是大于等于21(Android5.0)才会为true,这是因为在5.0之后,引进了RenderThread线程,专门用来渲染ui线程绘制好的数据,渲染完后,会提交到事先申请的bufferqueue中,然后当vsync-sf信号来的时候,sufaceflinger会去对应app的bufferqueue中取出前面提交的buffer数据,然后进行合成layer,最终屏幕(hwc)收到该请求后,进行图层进行合成,最终送到屏幕硬件上显示。这就是一针从创建到消费的过程。
由于ui线程把绘制好的数据绘制好后,会通知renderthread线程进行渲染,直到下一针来的时候才开始工作,此时会有主线程空闲的时候,recyclerview正是利用此空闲时间来进行预加载,而如果在下一针来临的时候,预加载还没有完成,那么此时会放弃此次的预加载,了解原理后,开始分析过程:
预加载的处理类是GapWorker类,一个线程对应一个GapWorker,它是存储在ThreadLocal中。GapWorker是一个runnable类,它如何要工作的话,是通过GapWorker.postFromTraversal工作的:
1
2
3
4
5
6
7
8
9
10
11
12
|
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!");
}
if (mPostTimeNs == 0) {
mPostTimeNs = recyclerView.getNanoTime();
recyclerView.post(this);
}
}
recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
}
|
可以看出来实际是通过recyclerview.post,然后传的是自己,因此会直接run方法。预加载时机在源码里面有两处调用了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Override
public boolean onTouchEvent(MotionEvent e) {
...
switch (action) {
...
case MotionEvent.ACTION_MOVE: {
...
if (mScrollState == SCROLL_STATE_DRAGGING) {
...
// 处于拖动状态并且存在有效的拖动距离时
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
}
break;
...
}
...
return true;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class ViewFlinger implements Runnable {
...
@Override
public void run() {
...
if (!smoothScrollerPending && doneScrolling) {
...
} else {
...
if (mGapWorker != null) {
mGapWorker.postFromTraversal(RecyclerView.this, consumedX, consumedY);
}
}
}
...
}
|
看下GapWorker的run方法如何实现的预加载:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@Override
public void run() {
try {
TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);
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;
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;
}
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();
}
}
|
在调用prefetch前传入nextFrameNs,该值表示预估的下一个vsync来临的时间,首先获取上一针的绘制起始时间,也就是latestFrameVsyncMs,而mFrameIntervalNs是通过刷新率算出来的一针需要的时间,比如60hz的手机,一秒是60帧,那么mFrameIntervalNs的值是一针需要16ms,mFrameIntervalNs的单位是纳秒,这个时间在开篇的onAttachedToWindow方法中计算的,所以可以看出来,nextFrameNs时间就是预加载最后的期限时间,超过这个时间就会放弃该预加载,上面提到的上一针时间是用过recyclerview的getDrawingTime获取的,它是获取的attachInfo的mDrawingTime时间,而mDrawingTime是在viewRootImpl中draw方法给赋值的:
1
2
3
4
5
6
|
private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
//省略代码
mAttachInfo.mDrawingTime =
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
//省略代码
}
|
可以看出来,它是通过Choreographer的getFrameTimeNanos方法来获取的:
1
2
3
4
5
|
@UnsupportedAppUsage
public long getFrameTimeNanos() {
//省略代码
return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
}
|
mLastFrameTimeNanos是在doFrame中将参数frameTimeNanos给赋值的,而frameTimeNanos参数表示的就是当前vsnyc信号来临的时间。所以最终结论就是通过遍历recyclerview的drawingtime,来获取最近一次的vsync时间,并加上当前设备一针所需要的时间,从而得到下一个vsync信号来临的时间,也就是预加载要完成的最晚时间。继续跟进GapWorker的prefetch方法:
1
2
3
4
|
void prefetch(long deadlineNs) {
buildTaskList();
flushTasksWithDeadline(deadlineNs);
}
|
buildTaskList,它是用来构建预加载的task列表:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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的position
view.mPrefetchRegistry.collectPrefetchPositionsFromView(view, false);
totalTaskCount += view.mPrefetchRegistry.mCount;
}
}
//省略代码
}
|
首先是调用了view.mPrefetchRegistry.collectPrefetchPositionsFromView(view, false)来收集预加载的view:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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 (!view.hasPendingAdapterUpdates()) {
layout.collectAdjacentPrefetchPositions(mPrefetchDx, mPrefetchDy,
view.mState, this);
}
//省略代码
}
}
|
可以看出来调用了layoutmanager的collectAdjacentPrefetchPositions的方法,把需要预加载的水平和竖直方向的偏移量传入其中,看下LinearLayoutManager的该方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Override
public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
LayoutPrefetchRegistry layoutPrefetchRegistry) {
int delta = (mOrientation == HORIZONTAL) ? dx : dy;
if (getChildCount() == 0 || delta == 0) {
// can't support this scroll, so don't bother prefetching
return;
}
ensureLayoutState();
final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDelta = Math.abs(delta);
updateLayoutState(layoutDirection, absDelta, true, state);
collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry);
}
|
通过方法来判断偏移量,然后把方向标识传入到updateLayoutState中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private void updateLayoutState(int layoutDirection, int requiredSpace,
boolean canUseExistingSpace, RecyclerView.State state) {
//省略代码
if (layoutToEnd) {
//获取可见的最后一个表项
final View child = getChildClosestToEnd();
//判断是否是reverse的
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
//获取预加载的position
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
//获取要预加载的表项与recyclerview底部的距离
scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
- mOrientationHelper.getEndAfterPadding();
} else {
//省略代码
}
//省略代码
mLayoutState.mScrollingOffset = scrollingOffset;
}
|
此处只保留从上到下的滑动,先是获取页面滚动的最后一个表项,然后判断是不是reverseLayout,如果不是则取LayoutState.ITEM_DIRECTION_TAIL(该值等于1),预加载的position等于最后一个表项的position+1,接着算出预加载的表项离recyclerview底部的距离。它是通过mOrientationHelper.getDecoratedEnd(child)- mOrientationHelper.getEndAfterPadding()得到的。
mOrientationHelper.getDecoratedEnd(child)它是在OrientationHelper中定义的createVerticalHelper方法中实现的:
1
2
3
4
5
6
|
@Override
public int getDecoratedEnd(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
view.getLayoutParams();
return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
}
|
1
2
3
|
public int getDecoratedBottom(@NonNull View child) {
return child.getBottom() + getBottomDecorationHeight(child);
}
|
getDecoratedBottom是获取child的bottom+child的底部间距高度,所以getDecoratedEnd是获取child的bottom+child的底部间距高度+下间距。
mOrientationHelper.getEndAfterPadding()也是在OrientationHelper中定义的createVerticalHelper方法中实现的:
1
2
3
4
|
@Override
public int getEndAfterPadding() {
return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
}
|
getEndAfterPadding是recyclerview的高度-recyclerview的下内边距。所以scrollingOffset是即将要预加载的表项离recyclerview底部的间距。接着看下collectPrefetchPositionsForLayoutState方法:
1
2
3
4
5
6
7
|
void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
LayoutPrefetchRegistry layoutPrefetchRegistry) {
final int pos = layoutState.mCurrentPosition;
if (pos >= 0 && pos < state.getItemCount()) {
layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset));
}
}
|
将刚才算的预加载的postion和偏移量传入到addPosition中,该方法是在GapWorker中实现的:
1
2
3
4
5
6
7
8
|
@Override
public void addPosition(int layoutPosition, int pixelDistance) {
//省略代码
final int storagePosition = mCount * 2;
mPrefetchArray[storagePosition] = layoutPosition;
mPrefetchArray[storagePosition + 1] = pixelDistance;
mCount++;
}
|
将position和偏移量成对保存在mPrefetchArray数组中。构建完数组中,接着就是将数组的数据存到task中。这块逻辑在buildTaskList中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private void buildTaskList() {
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()) {
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++;
}
}
}
|
task信息由由以下组成:
immediate:表示是否立即执行,判断依据预加载的表项离recyclerview底部距离是否小于滑动的速度
viewVelocity:滑动的速度
distanceToItem:预加载的表项离recyclerview底部距离
view:recyclerview
position:预加载表项的位置
从上面可以看出来从mPrefetchArray数组中取值是每两个值取出的,和上面build过程对应。所有的完事后,在buildTaskList中就是对task进行排序:
1
2
3
4
5
|
private void buildTaskList() {
...
// 3.对任务列表进行优先级排序
Collections.sort(mTasks, sTaskComparator);
}
|
上面就是整个buildTaskList的逻辑,接着就是根据构建的task来创建viewholder,该逻辑是在flushTasksWithDeadline方法中:
1
2
3
4
5
6
7
8
9
10
|
private void flushTasksWithDeadline(long deadlineNs) {
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();
}
}
|
flushTaskWithDeadline:
1
2
3
4
5
6
|
private void flushTaskWithDeadline(Task task, long deadlineNs) {
long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
task.position, taskDeadlineNs);
//省略代码
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
int position, long deadlineNs) {
RecyclerView.Recycler recycler = view.mRecycler;
RecyclerView.ViewHolder holder;
holder = recycler.tryGetViewHolderForPositionByDeadline(
position, false, deadlineNs);
if (holder != null) {
if (holder.isBound() && !holder.isInvalid()) {
//如果holder已经绑定过并且是可用的,加入到cacheview缓存中
recycler.recycleView(holder.itemView);
} else {
//否则加入到RecycledViewPool中
recycler.addViewHolderToRecycledViewPool(holder, false);
}
}
return holder;
}
|
上面通过tryGetViewHolderForPositionByDeadline获取viewholder,和正常获取viewholder的区别是传入了deadlineNs,直接看该方法是如何放弃超时的viewholder:
1
2
3
4
5
6
7
8
9
10
|
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
return null;
}
}
}
|
如果deadlineNs不是FOREVER_NS,普通调用该方法传入的deadlineNs是FOREVER_NS,所以是通过该值区分是不是预加载调用的,接着通过willCreateInTime返回值判断要不要放弃:
1
2
3
4
|
boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) {
long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs;
return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
}
|
expectedDurationNs取的是对应viewholder的平均创建时间,其实也不叫平均时间:
1
2
3
4
5
6
7
8
9
10
11
|
void factorInCreateTime(int viewType, long createTimeNs) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mCreateRunningAverageNs = runningAverage(
scrapData.mCreateRunningAverageNs, createTimeNs);
}
long runningAverage(long oldAverage, long newValue) {
if (oldAverage == 0) {
return newValue;
}
return (oldAverage / 4 * 3) + (newValue / 4);
}
|
每次将之前的创建viewholder时间占3分之4,当前创建的时间占1分之4。所以willCreateInTime的返回值是如果当前时间+平均创建viewholder时间小于最晚约定时间则不会放弃,否则直接放弃。如果不放弃接着会判断bind过程有没有超过约定时间:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
final int viewType = holder.getItemViewType();
long startBindNs = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
return false;
}
mAdapter.bindViewHolder(holder, offsetPosition);
long endBindNs = getNanoTime();
mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
return true;
}
|
bind过程是通过willBindInTime方法来判断有没有超过约定时间的,整个逻辑很清晰。
前面分析过如果holder是bind过的,则会加入到cacheview缓存中,否则加入到RecycledViewPool中,正常滑动的时候离屏的viewholder也是添加到cacheview缓存中的,那两者在缓存中又是怎么区分的呢?直接看recycler的recycleViewHolderInternal方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
void recycleViewHolderInternal(ViewHolder holder) {
//省略代码
//默认插入cacheview缓存的索引是末尾
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {//如果是正常离屏的viewholder
//默认指向最后一个元素
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);
//省略代码
}
|
默认插入cacheview缓存的索引是在末尾,在插入正常离屏的viewholder时候如果遇到预拉取的viewholder,则往前找直到最后一个离屏的viewholder,然后插入到它后面。所以不难看出,预拉取的会在后面,离屏的会在前面。这样的好处是预加载的viewholder由于在后面使用的机会会很大,放在集合的后面删除的概率要小。
参考:掌握这17张图,没人比你更懂RecyclerView的预加载