有时候看日志的时候,可能会在日志中看到类似下文的打印:
Skipped 30 frames! The application may be doing too much work on its main thread.
这句话的意思就是:该行日志打印之前的主线程存在耗时操作,导致掉了30帧。之前知道这句日志的意思,但是不知道系统是怎么计算的,于是专门花时间看了下 Choreographer 的源码。
职责:用于配合系统的 VSYNC 信号,用于接收系统发出的 VSYNC 信号,统一管理应用程序的输入、动画和绘制等任务的执行时机
初始化时机:
public ViewRootImpl(Context context, Display display) {
...
// 主线程初始化
mChoreographer = Choreographer.getInstance();
...
}
private static final ThreadLocal sThreadInstance =
new ThreadLocal() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
return new Choreographer(looper, VSYNC_SOURCE_APP);
}
};
Choreographer 的成员变量,在 Choreographer 的构造器中被初始化,为主线程 Handler
应用程序的 Input(主要是 Input 事件)、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作)任务,都会进入 FrameHandler 的消息队列等待执行
Choreographer 的成员变量,在 Choreographoer 的构造器中被初始化,用于接收 VSYNC 信号并进行相应处理
首先看下 FrameDisplayEventReceiver 类:
// 继承 DisplayEventReceiver,可以接收 VSYNC 信号
// 实现了 Runnable 接口,可以作为一个 Runnable 传入消息队列
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
// 接收到 VSYNC 信号,开始当前帧的绘制
...
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;
// 该类实现了 Runnable 接口,因此 this 代表该类,Handler 执行该 Message 时,就会执行下方 run() 方法
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
// mHandler 是 FrameHandler 对象,为主线程 Handler,即将本次 Runable 在指定时间 timestampNanos 在主线程执行
// 执行该任务前,会先将该任务之前的所有任务执行完
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
// 实际执行到该 Runnable 时,会调用 doFrame 方法,并将该方法计划执行时间 mTimestampNanos 作为参数传入
doFrame(mTimestampNanos, mFrame);
}
}
接下来看 doFrame 方法:
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
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();
// 该帧实际执行时间和计划执行时间的差值,即延迟时间
final long jitterNanos = startNanos - frameTimeNanos;
// mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()) 一般为 16.7ms,即每一帧的绘制时间
if (jitterNanos >= mFrameIntervalNanos) {
// 由此可以得出共计延迟了多少帧,就是掉了多少帧
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
// SKIPPED_FRAME_WARNING_LIMIT 默认为30,可以代码里面通过反射更改
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
frameTimeNanos = startNanos - lastFrameOffset;
}
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.");
}
scheduleVsyncLocked();
return;
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
try {
// 执行具体的 Input、Animation、Traversal Task
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} 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 信号之后,会向 FrameHandler 发送一条 message,message 中包含本帧计划执行的时间。等到 message 真正执行的时候,计算实际执行时间和计划执行时间的差值,除以16.7ms 之后就得到了丢帧数
从系统计算丢帧数的方式可以看出,丢帧的原因是由于本帧之前的一系列 task ,总计耗时导致的。因此处理卡顿问题的时候,不仅要关注耗时操作的优化,还要考虑任务的离散,同一时刻执行的任务太多也有可能造成卡顿。