前言
本文首发于掘金,有意向转发的同行可私信,想在Android领域有所进步的伙伴,可微信搜索个人的公众号「Android技术集中营」或扫码添加,有不定时福利等大家来拿。
1 从Activity启动流程引出Choreographer
在之前分析app启动耗时时,通过benchmark导出的trace文件时,如下图:
当Activity进入到onResume生命周期时,此时有一个类出现了,就是Choreographer,中文意思为编舞者,不知是否有伙伴们研究过这个类的作用,至少在我看到这个类的时候,第一反应就是与图像渲染展示相关,因为当Activity进入到onResume时,页面才会完全展示出来并且用户可交互,而且Choreographer出现之后,RenderThread也开始了绘制,所以我们从Activity启动流程中看下Choreographer这个类的作用。
1.1 重温View的添加流程
当Activity启动时,通过AMS一系列的进程间通信,最终会调用到ActivityThread的handleResumeActivity方法,在这个方法中,会执行View的添加流程,其实在之前关于WindowManager的文章中详细介绍过了,这里我们再重温一下。
java
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
//......
final Activity a = r.activity;
if (localLOGV) {
Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity
+ ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished);
}
final int forwardBit = isForward
? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
willBeVisible = ActivityClient.getInstance().willActivityBeVisible(
a.getActivityToken());
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
// Get rid of anything left hanging around.
cleanUpPendingRemoveWindows(r, false /* force */);
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
ViewRootImpl impl = r.window.getDecorView().getViewRootImpl();
WindowManager.LayoutParams l = impl != null
? impl.mWindowAttributes : r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
l.softInputMode = (l.softInputMode
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);
}
}
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler(new Idler());
}
在handleResumeActivity方法中,我们可以看到会将当前Activity的DecorView添加到WindowManager中,调用了WindowManager的addView方法,这个方法最终的调用就是在WidowManageGlobal
中。
WidowManageGlobal # addView
java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
//......
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
// 查找当前DecorView在mViews数组中的位置
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
IWindowSession windowlessSession = null;
// If there is a parent set, but we can't find it, it may be coming
// from a SurfaceControlViewHost hierarchy.
if (wparams.token != null && panelParentView == null) {
for (int i = 0; i < mWindowlessRoots.size(); i++) {
ViewRootImpl maybeParent = mWindowlessRoots.get(i);
if (maybeParent.getWindowToken() == wparams.token) {
windowlessSession = maybeParent.getWindowSession();
break;
}
}
}
if (windowlessSession == null) {
root = new ViewRootImpl(view.getContext(), display);
} else {
root = new ViewRootImpl(view.getContext(), display,
windowlessSession);
}
view.setLayoutParams(wparams);
// 往数组中添加View
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
addView方法是一个线程安全的方法,首先我们将DecorView添加到WindowManager时,首先会从mViews
数组中查找是否添加过这个View,如果添加过,那么就会抛出下面这个异常:
java
"View xxx has already been added to the window manager."
相信伙伴们都碰到过这个问题,所以在使用WindowManager的时候,不能重复添加同一个View,在添加之前需要判断当前View是否已经被添加到Window上。
如果没有添加过,那么就会创建ViewRootImpl,同时将DecorView加入mViews
和mRoots
数组中,因此在WindowManagerGlobal中是维护了当前进程中所有的页面窗口,在页面刷新时,便可以从mViews
中找到对应的窗口,做刷新处理即可。
最后是调用了ViewRootIml的setView方法,这个方法内容太多了,有兴趣的伙伴可以去看下源码,在这个方法中调用了requestLayout,这次调用是确保在系统接收到任意事件之前进行布局绘制。
java
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
1.2 第一次布局
前面我们提到了,调用ViewRootImpl的setView方法时,会执行requestLayout方法,在这个方法中,有一个非常重要的方法,就是scheduleTraversals
.
java
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
ViewRootImpl # scheduleTraversals
其实不止ViewRootImpl的setView方法会调用到scheduleTraversals,所有View的刷新都会调用到scheduleTraversals,我们看下这个方法。
java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
-
首先有一个标志位
mTraversalScheduled
的判断,也就是说当调用scheduleTraversals方法时,此标志位就会被设置为true,同一时间只会执行一次,直到执行完成,举个例子,当TextView的setText方法同时执行两次,那么也只会触发一次渲染逻辑。 -
通过Handler发送一个同步屏障,什么是同步屏障呢?就是通过Handler创建一个空消息,从View刷新这个时间点,到VSYNC信号来临这段时间内,屏蔽掉所有的同步消息,保证VSYNC消息来临时,立刻执行刷新,保证刷新的及时性。
-
接下来,到了本篇文章的重点,出现了Choreographer。
2 Choreographer的作用
首先我们先看,Choreographer是什么时候初始化的。
java
public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(),
false /* useSfChoreographer */);
}
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session) {
this(context, display, session, false /* useSfChoreographer */);
}
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
// ......
// TODO(b/222696368): remove getSfInstance usage and use vsyncId for transactions
mChoreographer = useSfChoreographer
? Choreographer.getSfInstance() : Choreographer.getInstance();
// ......
}
在创建ViewRootImpl的时候,就将Choreographer实例化了,我们可以看到Choreographer是一个单例,会根据useSfChoreographer的值选择实例化什么类型的对象,因为useSfChoreographer = false,因此会通过getInstance获取。
java
/**
* Gets the choreographer for the calling thread. Must be called from
* a thread that already has a {@link android.os.Looper} associated with it.
*
* @return The choreographer for this thread.
* @throws IllegalStateException if the thread does not have a looper.
*/
public static Choreographer getInstance() {
return sThreadInstance.get();
}
java
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
我们看到,Choreographer是线程单例的,因为是通过ThreadLocal存储,初始化时选择的vsyncSource为VSYNC_SOURCE_APP。
2.1 Choreographer的构造方法
java
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
// b/68769804: For low FPS experiments.
setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
}
- 首先创建了一个FrameHandler对象,主要用来接收消息处理事件,主要分为3类:
MSG_DO_FRAME
MSG_DO_SCHEDULE_VSYNC
MSG_DO_SCHEDULE_CALLBACK
java
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}
-
通过系统的标志位USE_VSYNC,判断是否需要创建FrameDisplayEventReceiver,默认是true;那么FrameDisplayEventReceiver的作用是什么呢?主要是用来接收VSYNC事件的,当VSYNC信号来临时,通知Choreographer。
可以将其理解为广播接收器的角色。
java
// Enable/disable vsync for animations and drawing.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769497)
private static final boolean USE_VSYNC = SystemProperties.getBoolean(
"debug.choreographer.vsync", true);
- 创建CallbackQueue数组
2.2 Choreographer # postCallback 请求VSYNC
当初始化完成之后,ViewRootImpl中的scheduleTraversals方法中,调用了Choreographer的postCallback.
java
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
java
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
最终调用到了postCallbackDelayedInternal方法中,会判断是否立即执行还是延期执行,如果立即执行,就会调用scheduleFrameLocked方法;如果是延期执行,那么最终还是会调用scheduleFrameLocked方法,mHandler就是我们介绍的在Choreographer构造方法中创建的FrameHandler。
java
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
在scheduleFrameLocked方法中,因为USE_VSYNC在Android4.1之后默认开启,所以就会判断是否在主线程,如果在主线程直接执行scheduleVsyncLocked方法;如果不是,那么就通过FrameHandler post消息到主线程,最终还是执行scheduleVsyncLocked方法。
java
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void scheduleVsyncLocked() {
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
mDisplayEventReceiver.scheduleVsync();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
在这个方法中,会调用FrameDisplayEventReceiver的scheduleVsync方法,此方法会向native层发起VSYNC信号请求任务。
java
@UnsupportedAppUsage
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
2.3 VSYNC信号的回调
前面我们讲到了,Choreographer调用postCallback,最终调用的是FrameDisplayEventReveiver的scheduleVsync方法,向VSYNC服务发起请求VSYNC信号刷新页面,那么VSYNC服务返回VSYNC信号是在FrameDisplayEventReveiver的onVsync方法中回调的。
java
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#onVsync "
+ vsyncEventData.preferredFrameTimeline().vsyncId);
}
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
// earlier than the frame time, then the vsync event will be processed immediately.
// Otherwise, messages that predate the vsync event will be handled first.
long now = System.nanoTime();
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
timestampNanos = now;
}
if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be "
+ "one at a time.");
} else {
mHavePendingVsync = true;
}
mTimestampNanos = timestampNanos;
mFrame = frame;
mLastVsyncEventData = vsyncEventData;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}
在onVsync方法中,是可以拿到VSYNC事件相关的数据信息,同时执行到run方法中,执行doFrame方法。
2.4 Choreographer # doFrame
doFrame方法执行是同步的,但是不要以为VSYNC'信号回调来的时候,doFrame就立即执行。 显然这种是最好的,能够最大限度的保证16.6ms内完成全部的渲染任务,但是如果主线程有耗时任务,那么doFrame就会被延迟,即便我们在刷新的时候设置了同步屏障,也不能解决此问题。
在doFrame方法中,有几个参数比较重要:
- frameTimeNanos:VSYNC信号来临的时间;
- vsyncEventData:VSYNC相关的数据,例如可以获取当前屏幕刷新率。
java
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
final long startNanos;
// 获取屏幕刷新速率
final long frameIntervalNanos = vsyncEventData.frameInterval;
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#doFrame " + vsyncEventData.preferredFrameTimeline().vsyncId);
}
FrameData frameData = new FrameData(frameTimeNanos, vsyncEventData);
synchronized (mLock) {
if (!mFrameScheduled) {
traceMessage("Frame not scheduled");
return; // no work to do
}
if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
mDebugPrintNextFrameTimeDelta = false;
Log.d(TAG, "Frame time delta: "
+ ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
}
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
//当前时间到VSYNC信号来临时间间隔
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= frameIntervalNanos) {
long lastFrameOffset = 0;
if (frameIntervalNanos == 0) {
Log.i(TAG, "Vsync data empty due to timeout");
} else {
lastFrameOffset = jitterNanos % frameIntervalNanos;
final long skippedFrames = jitterNanos / frameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main "
+ "thread.");
}
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (frameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f)
+ " ms in the past.");
}
}
frameTimeNanos = startNanos - lastFrameOffset;
frameData.updateFrameData(frameTimeNanos);
}
if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
traceMessage("Frame time goes backward");
scheduleVsyncLocked();
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
traceMessage("Frame skipped due to FPSDivisor");
scheduleVsyncLocked();
return;
}
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos,
vsyncEventData.preferredFrameTimeline().vsyncId,
vsyncEventData.preferredFrameTimeline().deadline, startNanos,
vsyncEventData.frameInterval);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
mLastFrameIntervalNanos = frameIntervalNanos;
mLastVsyncEventData = vsyncEventData;
}
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameData, frameIntervalNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameData, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameData,
frameIntervalNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameData, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameData, frameIntervalNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (DEBUG_FRAMES) {
final long endNanos = System.nanoTime();
Log.d(TAG, "Frame " + frame + ": Finished, took "
+ (endNanos - startNanos) * 0.000001f + " ms, latency "
+ (startNanos - frameTimeNanos) * 0.000001f + " ms.");
}
}
-
首先会判断当前时间到VSYNC信号来临时的间隔jitterNanos,如果jitterNanos超过了屏幕刷新速率frameIntervalNanos,例如当前屏幕刷新率为60FPS,即16.67ms,那么就会打印出来丢帧的日志。
如果丢帧的个数超过30帧(
SKIPPED_FRAME_WARNING_LIMIT
),那么我们在控制台中会看到下面的日志:
java
Skipped 119 frames! The application may be doing too much work on its main thread.
- 如果没有超时,那么就正常执行doCallbacks
java
void doCallbacks(int callbackType, FrameData frameData, long frameIntervalNanos) {
CallbackRecord callbacks;
long frameTimeNanos = frameData.mFrameTimeNanos;
synchronized (mLock) {
// We use "now" to determine when callbacks become due because it's possible
// for earlier processing phases in a frame to post callbacks that should run
// in a following phase, such as an input event that causes an animation to start.
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
// ......
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
c.run(frameData);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
前面我们提到过,在初始化Choreographer时,我们创建了CallbacksQueue数组,在调用postCallback时,其实会把对应的Runnable对象封装成一个CallbackRecord放在数组中,最终会遍历这个数组并执行CallbackRecord的run方法。
java
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
void run(FrameData frameData) {
if (token == VSYNC_CALLBACK_TOKEN) {
((VsyncCallback) action).onVsync(frameData);
} else {
run(frameData.getFrameTimeNanos());
}
}
这里我们可以看到,执行run方法时还是会区分对应的action类型,也就意味着Choreographer中可能存在多种类型的Callback,对应不同的Runnable对象,其中postCallback方法,传入的就是Runnable对象。
java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
所以在执行Choreographer的doFrame方法时,最终会执行mTraversalRunnable的run方法,执行doTraversal方法。
2.5 ViewRootImpl # doTraversal
在执行doTraversal方法时,会移除同步屏障,并执行performTraversals方法,同时会把mTraversalScheduled这个标志位置反,意味着此时可以再次进行View的刷新。
java
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
至于performTraversals中干了什么事,相信伙伴们都知道吧,就是经典的measure、layout、draw三大View的绘制流程了。
3 实时监控App帧率变化
通过前面我们对于Choreographer的理解,我们看下在上层应用中,我们可以通过哪些方法来监控app的帧率变化。
这里我们根据系统源码的这种方式,来计算从发起下一次VSYNC到下一次VSYNC来临时,丢帧的次数。
Kotlin
class FrameMonitorCallback(
@get:JvmName("callVsyncTime")
var callVsyncTime: Long
) : Choreographer.FrameCallback {
/** 默认设置为60FPS,16.67ms刷新一次 */
private val mFrameInterval = 1000000000 / 60f
/**
* VSYNC信号来临时,执行
* @param frameTimeNanos 接收到VSYNC信号时的时间
*/
override fun doFrame(frameTimeNanos: Long) {
Log.d("FrameMonitor", "doFrame: $frameTimeNanos")
if (callVsyncTime == 0L) {
callVsyncTime = System.nanoTime()
}
//计算两者的时间偏差
val jitter = frameTimeNanos - callVsyncTime
Log.d("FrameMonitor", "jitter: $jitter")
if (jitter >= mFrameInterval) {
//计算丢帧次数
val count = jitter / mFrameInterval
Log.d("FrameMonitor", "doFrame: lost frame $count")
}
// callVsyncTime = frameTimeNanos
//继续注册下一次vsync
// Choreographer.getInstance().postFrameCallback(this)
}
}
这里我们默认使用手机的屏幕刷新速率60FPS,模拟一次点击事件,首先发起VSYNC请求,因为一些任务导致主线程干了很多事,此时同步屏障是不能解决问题的,2s后刷新了UI。
Kotlin
binding.btnLogin.setOnClickListener {
Choreographer.getInstance().postFrameCallback(FrameMonitorCallback(System.nanoTime()))
Thread.sleep(2000)
binding.tv01.text = "好了"
}
从下面的日志看,与系统打印的丢帧次数是一致的,而且我们这次请求VSYNC就已经结束了,如果界面保持不变,那么doFrame就不会被执行,也就是意味着measure/layout/draw不会被执行,但是底层依然还是会保持16.6ms一次的刷新速率。
java
2023-11-11 16:48:30.851 3977-3977 Choreographer com.example.myapplication I Skipped 119 frames! The application may be doing too much work on its main thread.
2023-11-11 16:48:30.851 3977-3977 FrameMonitor com.example.myapplication D doFrame: 96936387090327
2023-11-11 16:48:30.851 3977-3977 FrameMonitor com.example.myapplication D jitter: 1997299320
2023-11-11 16:48:30.851 3977-3977 FrameMonitor com.example.myapplication D doFrame: lost frame 119.83796
只有当下一次页面开始刷新的时候,Choreographer会再次发起VSYNC请求,执行doFrame,所以我们要完成app的卡顿监控,需要密切观察屏幕的变化,在合适的时机注册VSYNC请求。
总结
当我们想要在TextView上显示文案,或者在ListView中显示列表数据时,都会涉及到View的刷新,那么整体的流程就是:
- 当View需要刷新的时候,调用invalidate最终会走到ViewRootImpl的scheduleTraversals方法中,此时设置同步屏障,通过Choreographer向VSYNC服务请求VSYNC刷新;
- 当VSYNC信号返回时,会执行Choreographer的doFrame方法,计算丢帧以及执行所有的CallbackRecord,也就是在调用
postCallback
或者postFrameCallback
时设置的Runnable对象或者FrameCallback,执行其run方法或者doFrame方法; - 当执行postCallback中的Runnable对象时,最终调用了ViewRootImpl的doTraversals方法,此时会移除同步屏障,取反标志位,并执行performTraversals方法绘制。