浅析RecyclerView
预加载RV-Prefetch
机制
UI
渲染基本流程(UI-Thread
,Render-Thread
,SurfaceFlinger
)(硬件加速开启)
当系统V-Sync
信号来临时,会唤醒主线程,回调编舞者Choreographer#FrameDisplayEventReceiver#onVsync()
开始这一帧的绘制,依次处理input
\ animation
\ measure
\ layout
\ draw
, 然后同步sync
到RenderThread
(将这些操作整合成RenderNode
),然后RenderThread
将这些绘制指令转成OpenGL
指令,最终通过GPU
执行绘制到Graphic Buffer
,然后SurfaceFlinger
拿到buffer
后进行layer
的整合,最终把数据交给HAL
层绘制到屏幕上。
什么时候预加载?加载什么?
我们先来看一下系统处理V-SYNC
的System-Trace
可以看到,俩次V-SYNC
处理中是有主线程空闲状态的。为了更好丝滑的滑动,除了RV
的缓存机制,其中还有一个预加载机制。缓存机制就是缓存VH
,防止每一次都需要创建VH
(也就是创建View
),那么在上面的图中,能不能在不影响主线程的流程情况下,提前创建呢?很显然,在俩次处理中间,有一个等待V-SYNC
来临的间隔时间—gap
,就是在这一段里,提前尝试创建VH
。
预加载:RV-Prefetch
默认情况下,RV
的预加载是开启的,现在让我们滑动RV
,看下系统是怎么处理的(使用AS
自带的Profile
):
可以看到,在俩次V-SYNC
处理之间,原本空闲的主线程,处理一个新任务—预加载
!
我们来跟踪一下代码,从滑动开始,即RecyclerView
的onTouchEvent()
中处理ACTION_MOVE
:
RecyclerView 1.2.1
// 21以上默认开启
static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21;
// onAttachToWindow()时初始化
// GapWork:一个Runnable,见缝插针
GapWorker mGapWorker;
@Override
protected void onAttachedToWindow() {
// ...
if (ALLOW_THREAD_GAP_WORK) {
// Register with gap worker
mGapWorker = GapWorker.sGapWorker.get();
if (mGapWorker == null) {
mGapWorker = new GapWorker();
//...
}
}
}
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (action) {
case MotionEvent.ACTION_MOVE: {
// ....
if (mGapWorker != null && (dx != 0 || dy != 0)) {
// 检测到滑动,借RecyclerView,post一个runnable到doFrame后面,也就是上面的RV-Prefetch
mGapWorker.postFromTraversal(this, dx, dy);
}
}
}
}
来瞅瞅GapWorker#run()
里干了什么:
@Override
public void run() {
try {
// 一开始就做了trace标记 "RV Prefetch"
TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);
// ...
// 下一次信号来临的时间
long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;
// 调用prefetch()函数,预加载
prefetch(nextFrameNs);
// TODO: consider rescheduling self, if there's more work to do
} finally {
mPostTimeNs = 0;
TraceCompat.endSection();
}
}
预加载核心流程:
1.判断需要预加载那种类型的ViewHolder
2.获取该类型的ViewHolder
历史平均创建耗时 t1
3.获取距离下一次V-SYNC
来临时的时间 t2
4.判断 t1
是否小于t2
,即能不能赶在下一次信号来临时创建ViewHolder
成功
5.满足4,则创建该类型的ViewHoldewr
6.在5完成后,如果距离下一次信号来临时,还有时间,则尝试绑定ViewHolder
GapWorker
private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
int position, long deadlineNs) {
// ....
RecyclerView.Recycler recycler = view.mRecycler;
RecyclerView.ViewHolder holder;
try {
view.onEnterLayoutOrScroll();
// 获取VH tryGetViewHolderForPositionByDeadline
holder = recycler.tryGetViewHolderForPositionByDeadline(
position, false, deadlineNs);
// 如果预加载成功,根据情况存入mCachedViews或mRecyclerPool
if (holder != null) {
if (holder.isBound() && !holder.isInvalid()) {
// 已经绑定了数据,则存入 mCachedViews
recycler.recycleView(holder.itemView);
} else {
// 没有绑定数据则存入 mRecylerPool
recycler.addViewHolderToRecycledViewPool(holder, false);
}
}
} finally {
view.onExitLayoutOrScroll(false);
}
return holder;
}
RecyclerView.Recycler.tryGetViewHolderForPositionByDeadline()
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
// ...
ViewHolder holder = null;
// ...
if (holder == null) {
// ....
if (holder == null) {
long start = getNanoTime();
// mRecyclerPool.willCreateInTime(type, start, deadlineNs) 能不能赶得及
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
// 不能的话,直接返回
return null;
}
// 赶得及话,创建ViewHolder
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");
}
}
}
//..
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()) {
// ..
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
// 还有剩余时间,尝试再绑定一下
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
}
OK,RecyclerView
预加载机制核心流程就这些,更加详细的预加载配置可以在LayoutManager
中寻找(开启,加载个数等)。