源码基于 SDK 29。
VSYNC 信号
VSYNC 即屏幕垂直同步信号,在屏幕每次刷新显示界面完成之后都会发出该信号,屏幕渲染系统会监听该信号,以便渲染下一帧需要显示的内容。
屏幕每秒钟发出 VSYNC 信号的次数即为常说的屏幕刷新率,目前一般屏幕的刷新率为 60hz,即每秒钟会发出 60次VSYNC信号。屏幕内容渲染系统,需要在两次 VSYNC 信号的间隙时间内,渲染好下一帧屏幕需要显示的内容。以 60hz 刷新率为例,渲染系统需要在 1/60 秒(即0.01666秒,16.6毫秒 )内完成一帧内容的渲染。
关于帧率,还是以 60hz 刷新率为例,如果操作系统每次都能在 16.6 毫秒内完整一帧屏幕内容的渲染,那么这时的帧率就为 60fps,如果再 16.6毫秒内只完成了50帧内容的渲染,那么帧率就是 50fps。
关于卡顿,基于上述是不是只要帧率低于60FPS 就为卡顿你?并不是这样的。我们平时观看的电影、电视剧一般是 30fps ,这是因为人眼睛感觉物体运动不连续的最低标准是24fps,因此即使电影只有30fps人眼也不会感觉电影画面不连续。所以,帧率低于60fps 并不意味着卡顿,但低于 24fps 就能让守护感觉手机界面不连续形成卡顿的感观。人感觉画面不连续有最低标准,但帧率越高人眼观感是越舒服的,所以现在才会有 120hz、144hz 刷新率的手机成为Android旗舰机标配这一情况。
Choreographer
Choreographer
是用来配合 VSYNC 信号完成内容绘制和渲染的。VSYNC 信号由该类对象进行处理和申请,配合其实现该目标的类还有如下:
- FrameDisplayEventReceiver & DisplayEventReceiver
这两个类是继承关系,作用是申请处理 VSYNC 信号和 VSYNC 信号来临时发送 Handler 消息告知 Choregrahper 处理 VSYNC 信号。
- CallbackQueue & CallbackRecord
一个是VSYNC 信号回调队列,一个事回调队列中的子项。用来保存需要在 VSYNC 到来时需要通知到的对象。
创建过程
线程单例
Choreographer
使用 ThreadLocal
实现了线程单例,可以直接看它的 getInstance()
函数。
public static Choreographer getInstance() {
return sThreadInstance.get();
}
复制代码
构造函数
构造函数需要两个参数,一个是 Looper
,其内部会创建一个 Handler 来用处理 VSYNC 信号和回调。另一个是 source ,表示请求 VSYNC 信号的源。
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// 传入 Looper ,初始化 Hanlder
mHandler = new FrameHandler(looper);
// 重要步骤,注册 VSYNC 信号监听
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));
}
复制代码
Handler 处理三种消息,分别处理不同的事务。
private final class FrameHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
// 当 VSYNC 来临时处理
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
// 申请 VSYNC 信号
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
// 需要延迟的任务,就是构造了一个延时执行的 Message ,最后还是调用了上面两个。
doScheduleCallback(msg.arg1);
break;
}
}
}
复制代码
回调任务分类
在构造函数中我们了解到了五种需要 VSYNC 信号回调任务(后续都将需要 VSYNC 执行的任务称回调任务),这里先掌握一下。总共是五种,分别对应不同的任务,有着不同的执行优先级。
// 1. 处理用户输入事件,优先级最高
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
// 2. 处理动画,值动画 ValueAnimator 用的就是这个。
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 3. 处理动画回调
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
// 4. 处理 ViewRootImpl 的 traversal,即处理 ui 绘制任务
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 5. 处理提交任务,我们需要时一般用这个。
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
复制代码
请求 VSYNC 信号
VSYNC 信号在屏幕被点亮后就不停的发出, Choreographer 不可能每时每刻都去响应,而 VSYNC 也不会总是分配每一个 Choreographer。因此当需要时 Choreograhper 才会去申请 VSYNC 信号来处理,申请的入口就是外界主动向 Choreographer 提交,需要 VSYNC 信号的任务时,即 postXX() 系列函数。
postCallbackDelayedInternal() 函数
一些列的 postXX()
函数,最终都会调用此函数。该方法会将需要 VSYNC 信号执行的回调任务,按照分类加入对应的队列中。保存这里使用到了 CallbackQueue 和 CallbackRecord 类,不同类型的回调任务对应不同的队列,添加到队列中时按照执行时间的先后顺序,执行时间相同就按加入时间先后排列。
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
// 将需要执行的回调保存到对应的回调组中
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
// 立即请求 VSYNC 信号进行处理
scheduleFrameLocked(now);
} else {
// 需要延时执行,发送延迟消息。注意,该消息是异步的。
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
复制代码
scheduleFrameLocked() 函数
该函数目的是调用 DisplayEventReceiver.scheduleVsync()
完成申请 vsync 信号,scheduleVaync() 也是直接调用了 native 函数。
public void scheduleVsync() {
nativeScheduleVsync(mReceiverPtr);
}
复制代码
VSYNC 来了,进行处理
onVsync() 函数
当 VSYNC 信号到来时,native 层首先会回调 DisplayEventReceiver.dispatchVsync()
函数,其中会调用 onVsync()
函数,该函数是个抽象函数,其实现在子类 FrameDisplayEventReceiver 中。
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
onVsync(timestampNanos, physicalDisplayId, frame);
}
复制代码
onVsync() 函数,向 Handler 发送了一个异步消息回调消息其回调为该类本,回调 run() 函数中直接调用了 doFrame() 方法。
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
······
// 发送异步消息,异步回调为自身,因此当异步回调执行时会回执行 run() 方法。
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
复制代码
doFrame() 函数
该函数的作用有两个,主要的是按照先后顺序执行回调任务,二个是计算前后帧的耗时,判断是否掉帧或者出现异常情况重新申请VSYNC 信号。
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
······ // 耗时计算,计算两次 vsync 信号间隙耗时,即一帧渲染耗时。
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
// 1. 处理用户输入事件,优先级最高
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
// 2. 处理动画
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 3. 处理动画回调
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
// 4. 处理 ViewRootImpl 的 traversal,即处理 ui 绘制任务
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 5. 处理提交任务
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
复制代码
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
······
// 根据 type 获取对应的回调任务队列
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
// 遍历执行回调任务
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(frameTimeNanos);
}
}
复制代码
总结
Choreographer 的源码阅读并不是很难,我们阅读理解了以后,对于Android 的渲染然机制有了更近一步的了解,对于我们在平时工作中构建出更加流畅的UI界面也是有一定的帮助。另外,在 Choreograhper 的分析之后,我们可以利用这个机制,实现一套简单 FPS 监控。
这边文章是我系统学习Android UI 渲染系统的第一篇分享,后续我也会将自己的学习思考分享出来,如果你发现了错误的地方还请不吝赐教。