文章目录
一、概述
背景:
在引入 Vsync 之前的Android 系统,系统渲染一帧是通过Message来实现的,Msg中间没有间隔(即上一帧绘制完,下一帧的Msg紧接着就开始被处理)。但由于Msg的消费时长存在变化,这直接导致了帧率不稳定(即1s内消费Msg的个数不稳定)。从而产生了Jank现象,画面撕裂等问题。
为了优化这些问题,Android 在4.1之后引入了
Choreographer + SurfaceFlinger + Vsync + TripleBuffer
这一套机制,保证了 Android App 可以以一个稳定的帧率运行(60fps),减少帧率波动带来的不适感。
本文我们来具体探究一下 Choreographer 的功能。
源码版本: Android 29
关联文章:
二、Choreographer
2.1 Choreographer的作用
Android 系统在4.1引入 Choreographer 的目的是为了配合 Vsync,给上层 App 的渲染提供一个稳定的 Message 处理的时机。因此 Choreographer 在 Android 渲染链路中起到一个承上启下的作用。
- 承上: 负责接收和处理 App(ViewRootImpl) 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。
- 如:集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等。
- 启下: 负责请求和接收 Vsync 信号。
- 请求 Vsync:
FrameDisplayEventReceiver.scheduleVsync()
。 - 接收 Vsync 事件回调:
FrameDisplayEventReceiver.onVsync()
。
- 请求 Vsync:
接下来我们看下Choreographer在收到一个Vsync信号后执行的绘制任务。图片来源:Android 基于 Choreographer 的渲染机制详解
2.2 Choreographer 的工作流程
- Choreographer 的初始化
1. 初始化 FrameHandler,绑定 Looper。
2. 初始化 FrameDisplayEventReceiver,与 SurfaceFlinger 建立通信用于接收和请求 Vsync。
3. 初始化 CallBackQueues。- SurfaceFlinger 的 appEventThread 唤醒发送 Vsync ,Choreographer 回调。FrameDisplayEventReceiver.onVsync , 进入 Choreographer 的主处理函数 doFrame。
- Choreographer.doFrame 计算掉帧逻辑。
- Choreographer.doFrame 处理 input 类型的 callback。
- Choreographer.doFrame 处理 animation 类型的 callback。
- Choreographer.doFrame 处理 insets animation 类型的 callback。
- Choreographer.doFrame 处理 traversal 类型的 callback。
- traversal-draw 中 UIThread 与 RenderThread 同步数据。
- Choreographer.doFrame 处理 Choreographer 的第五个 callback : commit。
- RenderThread 处理绘制命令,将处理好的绘制命令发给 GPU 处理。
- 调用 swapBuffer 提交给 SurfaceFlinger 进行合成。
三、源码解析
上面我们简单介绍了一下 Choreographer 的作用,以及Choreographer在接收到Vsync信号后触发的任务执行流程,下面我们具体看看Choreographer的代码实现。
3.1 App侧的渲染流程
在 Window系列 (一) — WindowManager 详解 一文中,我们介绍了 ViewRootImpl.scheduleTraversals() 内通过
Choreographer.postCallback(FrameCallback)
方法设置监听来触发回调逻辑。当 Choreographer 接收到 Vsync 信号时,会触发 TraversalRunnable 任务,最终执行 doTraversal() 方法进行View树的measure、layout、draw
的流程。
下图是绘制一帧界面的完整流程图:
在分析
Choreographer.postCallback(FrameCallback)
方法之前,我们先来看下 Choreographer 的初始化逻辑。
3.2 Choreographer初始化
// Thread local storage for the SF choreographer.
private static final ThreadLocal<Choreographer> sSfThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
// 获取当前线程的Looper
Looper looper = Looper.myLooper();
//...
return new Choreographer(looper, VSYNC_SOURCE_SURFACE_FLINGER);
}
};
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// 1.初始化FrameHandler
mHandler = new FrameHandler(looper);
// 2.初始化DisplayEventReceiver,用于接收Vsync信号。
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
// 记录最近一次收到Vsync信号的时间,单位:纳秒。
mLastFrameTimeNanos = Long.MIN_VALUE;
// 定义每一帧的时间长度,如果每秒60Hz,则时间长度为16.66ms。
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
// 存放input、animation、traversal等事件的callback回调。
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
private final class FrameHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME: //开始渲染下一帧的操作
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC: //请求Vsync信号
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK: //处理Callback
doScheduleCallback(msg.arg1);
break;
}
}
}
小结:
- Choreographer 是个单例,每个线程独有(即线程间不共享)。
- 初始化Msg处理的Handler:FrameHandler。
- 初始化Vsync信号的接收器:FrameDisplayEventReceiver。
- 初始化 input、animation、traversal等事件的callback回调队列(mCallbackQueues)。
3.3 Choreographer.postCallback()
触发Choreographer.postCallback()的时机:
ViewRootImpl.scheduleTraversals() 内通过 Choreographer.postCallback(FrameCallback) 方法设置监听来触发回调逻辑。
// ViewRootImpl.class
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 这里传入CALLBACK_TRAVERSAL的callback类型。
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,
mTraversalRunnable, null);
// ...
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 执行测量、布局、绘制的逻辑。
doTraversal();
}
}
通过postCallback注册Callback:
Choreographer.postCallback()
-> Choreographer.postCallbackDelayed()
-> Choreographer.postCallbackDelayedInternal()
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
// mCallbackQueues是一个数组+链表的结构。
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
// 调用postCallback方法时,delayMillis传入0,所以dueTime == now成立。
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);
}
}
}
发起Vsync请求:
Choreographer.scheduleFrameLocked()
-> Choreographer.scheduleVsyncLocked()
-> DisplayEventReceiver.scheduleVsync()
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
// 避免在同一帧的时间区间内多次触发此逻辑(即每16.66ms内只会执行一次)。
mFrameScheduled = true;
if (USE_VSYNC) {
// 收发Vsync在同一个线程(即Looper相同),则直接执行,否则通过Handler进行线程间切换。
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);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
private void scheduleVsyncLocked() {
// 通过DisplayEventReceiver发送Vsync信号的请求。
mDisplayEventReceiver.scheduleVsync();
}
小结:
- 通过 Choreographer.postCallback() 方法设置回调,回调中执行测量、布局、绘制等逻辑。
- 当完成第1步的Callback回调设置后,接下来会调用 DisplayEventReceiver.scheduleVsync() 方法来发送Vysnc信号的请求。
3.4 FrameDisplayEventReceiver
前面我们提到,FrameDisplayEventReceiver是一个Vsync信号的接收器,它继承自DisplayEventReceiver,里面主要有三个方法比较重要,分别是:
- 发送接收Vsync的请求:
DisplayEventReceiver.scheduleVsync()
- 接收Vsync信号:
DisplayEventReceiver.dispatchVsync() -> DisplayEventReceiver.onVsync()
- 接收Vsync信号后具体的渲染:
FrameDisplayEventReceiver.run()
发送Vsync信号的请求: DisplayEventReceiver.scheduleVsync()
// DisplayEventReceiver.class
public void scheduleVsync() {
// mReceiverPtr为Native层接收数据对象的指针
nativeScheduleVsync(mReceiverPtr);
}
private static native void nativeScheduleVsync(long receiverPtr);
接收Vsync信号:
DisplayEventReceiver.dispatchVsync()
-> DisplayEventReceiver.onVsync()
// DisplayEventReceiver.class
// Called from native code.
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
onVsync(timestampNanos, physicalDisplayId, frame);
}
// FrameDisplayEventReceiver.class
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
long now = System.nanoTime();
if (timestampNanos > now) {
timestampNanos = now;
}
if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be one at a time.");
} else {
mHavePendingVsync = true;
}
// 记录接收到Vsync信号的时间戳,用于在doFrame方法中计算掉帧逻辑。
mTimestampNanos = timestampNanos;
mFrame = frame;
// 注意这里给Msg设置了Callback,所以最终在消费Msg时,会默认执行这个callback,即FrameDisplayEventReceiver.run()方法。
Message msg = Message.obtain(mHandler, this);
// 这里开启了Msg的同步消息屏障,可以提升渲染类消息的消费速度。
msg.setAsynchronous(true);
// 这里发送消息。
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
// FrameDisplayEventReceiver.class
public void run() {
// 到这里才消费上面发送的消息。
mHavePendingVsync = false;
// doFrame内会做一些丢帧的记录,已经callback的消费。
doFrame(mTimestampNanos, mFrame);
}
小结:
- DisplayEventReceiver.dispatchVsync() 方法接收到 Vsync 信号后,通过Handler发送消息的方式来往下传递,最终执行 Choreographer.doFrame() 里面的逻辑。
- 从Handler发送第1步消息开始到该消息被消费,这部分的时间往往依赖与这段时间内MQ中的Msg执行情况,如果这部分时间比较长,就可能会导致丢帧的情况。
- 第2步中提到当前的Msg从发送到最终消费之间存在空隙,所以 onVsync() 方法会向下传递接收到 Vsync信号的时间戳,用于在Choreographer.doFrame() 方法中计算掉帧的逻辑。
3.5 Choreographer.doFrame()
// Choreographer.class
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
long intendedFrameTimeNanos = frameTimeNanos;
// 记录当前的时间。
startNanos = System.nanoTime();
// 计算从App端接收到Vsync信号开始到执行doFrame方法的时间开销。
final long jitterNanos = startNanos - frameTimeNanos;
// 差值>=一帧的时间周期(16.6ms)
if (jitterNanos >= mFrameIntervalNanos) {
// 计算出丢了多少帧。
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
// 计算出当前时间距离最近一帧开始时间的偏移量。
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
// 将
frameTimeNanos = startNanos - lastFrameOffset;
}
//...
if (mFPSDivisor > 1) { //mFPSDivisor默认是1
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();
return;
}
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
// 还原状态,在scheduleFrameLocked方法中设置为true.
mFrameScheduled = false;
// 将最近的一帧开始时间赋值给它。
mLastFrameTimeNanos = frameTimeNanos;
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
// 回调input类型的callback
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
// 回调animation类型的callback
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
// 回调traversal类型的callback,我们在前面注册的监听回调就是在这里触发的。
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 回调commit类型的callback
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
小结:
Choreographer.doFrame() 方法就做了2件事:
- 计算掉帧逻辑。
- 回调各种callback:input、animation、insets animation、traversal、commit。
3.6 Choreographer.doCallbacks()
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
// 1.返回当前这一帧需要消费的callback回调,因为CallbackQueues是一个链表,所以只要返回头节点即可。
// 1.1 先根据callbackType类型从mCallbackQueues获取对应的链表数据。
// 1.2 传入当前的时间作为参考点,从头开始遍历,找到第1个时间晚于当前时间的callback,将它设置为新的头结点,并断开这个新头结点与前一个节点的连接,并返回之前的头结点。
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
if (jitterNanos >= 2 * mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ mFrameIntervalNanos;
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
// 2.遍历指定callbackType类型的callback链表,依次进行回调。
for (CallbackRecord c = callbacks; c != null; c = c.next) {
// 这里触发CallbackRecord.run(),在其内部真正触发我们设置的callback回调。
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
// 3.对触发过callback回调的对象进行回收。
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
小结:
Choreographer.doCallbacks() 方法主要做了3件事:
- 返回当前这一帧需要消费的callback回调,因为CallbackQueues是一个链表,所以只要返回头节点即可。
- 遍历上一步返回的头结点,触发对应的callback回调。
- 对触发过callback回调的对象进行回收。
四、总结
-
Choreographer 是线程单例的,而且必须要和一个 Looper 绑定,因为其内部有一个 Handler 需要和 Looper 绑定,一般是 App 主线程的 Looper 绑定
-
DisplayEventReceiver 是一个 abstract class,其 JNI 的代码部分会创建一个IDisplayEventConnection 的 Vsync 监听者对象。这样,来自 AppEventThread 的 VSYNC 中断信号就可以传递给 Choreographer 对象了。当 Vsync 信号到来时,DisplayEventReceiver 的 onVsync 函数将被调用。
-
DisplayEventReceiver 还有一个 scheduleVsync 函数。当应用需要绘制UI时,将首先申请一次 Vsync 中断,然后再在中断处理的 onVsync 函数去进行绘制。
-
Choreographer 定义了一个 FrameCallback interface,每当 Vsync 到来时,其 doFrame 函数将被调用。这个接口对 Android Animation 的实现起了很大的帮助作用。以前都是自己控制时间,现在终于有了固定的时间中断。
-
Choreographer 的主要功能是,当收到 Vsync 信号时,去调用使用者通过 postCallback 设置的回调函数。目前一共定义了五种类型的回调,它们分别是:
- CALLBACK_INPUT : 处理输入事件处理有关
- CALLBACK_ANIMATION : 处理 Animation 的处理有关
- CALLBACK_INSETS_ANIMATION : 处理 Insets Animation 的相关回调
- CALLBACK_TRAVERSAL : 处理和 UI 等控件绘制有关
- CALLBACK_COMMIT : 处理 Commit 相关回调,主要是是用于执行组件 Application/Activity/Service 的 onTrimMemory,在 ApplicationThread 的 scheduleTrimMemory 方法中向 Choreographer 插入的;另外这个 Callback 也提供了一个监测一帧耗时的时机。
说明: 第一步初始化完成后,后续就会在步骤 2-9 之间循环。