8种机械键盘轴体对比
本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?
Choreographer可以认为是连接底层和应用层的中间人角色。对下,它负责注册并接收底层发送的Vsync信号;对上,负责在应用层协调下一帧的绘制、事件、动画过程。Choreographer配合SurfaceFlinger、Triple Buffer为Android系统提供稳定的帧率刷新环境。
Choreographer初始化
Vsync信号简介
VSYNC 信号可以认为是一种定时中断信号。为了配合主流屏幕60Hz的刷新频率,即16.6ms一次,系统也控制Vsync信号的周期为16.6ms。Vsync信号在应用层会唤醒Choreographer响应用户输入、APP绘制等操作,在合成层会唤醒SurfaceFlinger,将准备好的Surface进行合成操作。
Vsync信号主要由硬件HWC(Hardware Composer,硬件混合渲染器)生成。然后通过回调将事件发送到SurfaceFlinger和Choreographer。1
2typedef void (*HWC2_PFN_VSYNC)(hwc2_callback_data_t callbackData,
hwc2_display_t display, int64_t timestamp);
DispSync将硬件Vsync信号转换成Choreographer和SurfaceFlinger使用的VSYNC和SF_VSYNC信号。
Choreographer初始化调用链
通过AS Profiler观察方法调用链,Activity启动后,执行完ActivitityThread.performResumeActivity(),再执行WindowManagerImpl.addView(),在其内部会执行ViewRootImpl的初始化。Choreographer的初始化就是在ViewRootImpl的构造方法中执行的。
1
2
3
4
5android.view.ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mChoreographer = Choreographer.getInstance();
}
后续代码为了可读性,都只展示了关键部分。
Choreographer初始化过程
-> 初始化FrameHandler,绑定Looper,响应事件
-> 初始化FrameDisplayEventReceiver,与SurfaceFlinger建立联系,响应、调度Vsync
-> 初始化CallbackQueue1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38android.view.Choreographer.java
// 使用ThreadLocal保证Choreographer线程单例,但一般是主线程
private static final ThreadLocal sThreadInstance =
new ThreadLocal() {
@Override
protected Choreographer initialValue() {
// Choreographer所在线程必须有Looper,因为很多任务调度是通过FrameHandler执行的
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;
}
};
// 初始化
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
// 上一帧绘制时间点
mLastFrameTimeNanos = Long.MIN_VALUE;
// 帧间隔,16.6ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
// 任务调度的回调都是放到该CallbackQueue中
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
执行核心方法doFrame()和调度Vsync都是通过Handler实现。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private 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:
doScheduleVsync();// 请求下一个Vsync信号
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}
FrameDisplayEventReceiver
Choreographer对Vsync信号的响应主要体现在FrameDisplayEventReceiver。这个类负责对Vsync信号注册、调度、响应。
FrameDisplayEventReceiver有三个主要方法:onVsync():响应Vsync信号
scheduleVsync():调度Vsync信号
run():执行doFrame(),doFrame()内部会做计算掉帧、响应Input、Animation、Traversal(measure/layout/draw)、Commit
响应Vsync
HWC触发Vsync信号后,在Java层会回调DisplayEventReceiver.onVsync(),该方法是空实现,本质上走到子类的FrameDisplayEventReceiver.onVsync()方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
scheduleVsync();
return;
}
// Vsync信号到来的时间戳,后面计算掉帧时会用到
mTimestampNanos = timestampNanos;
mFrame = frame;
// Message的Callback为自身,即FrameDisplayEventReceiver
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
// FrameHandler
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
本质上是通过FrameHandler发送了一个Callback为FrameDisplayEventReceiver自身的message,当Looper执行到该消息时会调用FrameDisplayEventReceiver.run()方法,该Looper一般是主线程的Looper。
其中mTimestampNanos记录了Vsync信号到来的时间点,后面计算掉帧时会用到。
run()1
2
3
4
5@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
内部调用Choreographer.doFrame().
Choreographer处理每一帧
doFrame()是Choreographer处理每一帧操作的核心。
内部先进行掉帧计算,记录帧信息,然后执行INPUT(用户输入)、ANIMATION、TRAVERSAL(measure/layout/draw)、COMMIT等回调。INPUT:输入事件
ANIMATION:动画
TRAVERSAL:页面刷新,响应measure、layout、draw
COMMIT:处理commit回调,修正最后一帧的时间戳
掉帧计算
掉帧计算的逻辑在下面这段代码,Vsync信号到来时记录一个开始时间,即onVsync()中的mTimestampNanos。在主线程MessageQueue中轮到doFrame()的message执行后(Vsync信号到来有可能不会立马处理,因为这中间主线程可能被其他任务占用),在doFrame()中记录一个结束时间,即下面代码中的startNanos。这两个时间差值就是Vsync信号的处理时延,即掉帧时间。
平时开发中大家有可能在Logcat看到下面这个log:”Skipped N frames! The application may be doing too much work on its main thread.” 掉帧超过30帧之后就会出这个提示。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/**
* @param frameTimeNanos 底层Vsync信号到达的时间戳
*/
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
// mFrameScheduled调度Vsync的标记位,在scheduleFrameLocked()置为true,防止不必要的调用
if (!mFrameScheduled) {
return; // no work to do
}
long intendedFrameTimeNanos = frameTimeNanos;
// 真正处理Vsync对应的某一帧的时间点
startNanos = System.nanoTime();
// 响应Vsync的时间点 - Vsync到来的时间点 = 耽误的时间,即掉帧时间
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
// 掉帧超过30帧,Log提示
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;
frameTimeNanos = startNanos - lastFrameOffset;
}
....
}
回调的逻辑在doCallback()中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/**
* @param frameTimeNanos 底层Vsync信号到达的时间戳
*/
void doFrame(long frameTimeNanos, int frame) {
....
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
// 响应输入,执行大家熟悉的View.dispatchOnTouchEvent一系列流程
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
// 响应动画
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 响应绘制流程,即measure、layout、draw
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 处理commit回调,修正最后一帧的时间戳
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
INPUT调用栈
有用户input交互后,从method trace可以看到,doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos)内部执行的是ConsumeBatchedInputRunnable。最终会走到DecorView.dispatchTouchEvent(),后续就是一系列touch event的事件分发逻辑。
ConsumeBatchedInputRunnable相关代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22android.view.ViewRootImpl.java
final class ConsumeBatchedInputRunnable implements Runnable {
@Override
public void run() {
doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
}
}
void doConsumeBatchedInput(long frameTimeNanos) {
if (mConsumeBatchedInputScheduled) {
mConsumeBatchedInputScheduled = false;
if (mInputEventReceiver != null) {
if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
&& frameTimeNanos != -1) {
scheduleConsumeBatchedInput();
}
}
doProcessInputEvents();
}
}
根据method trace跟一下代码,最后会走到View.dispatchPointerEvent1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20android.view.ViewRootImpl.java
/**
* Delivers post-ime input events to the view hierarchy.
*/
final class ViewPostImeInputStage extends InputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
...
processPointerEvent(q);
...
}
private int processPointerEvent(QueuedInputEvent q) {
...
// 后续走到View层的事件分发逻辑
boolean handled = mView.dispatchPointerEvent(event);
...
}
}
ANIMATION调用栈
如果使用ValueAnimator执行动画,可以看到下图中的方法调用栈:
动画执行时,Choreographer.doFrame()会响应AnimationHandler内部的Choreographer.FrameCallback.doFrame(),然后执行到ValueAnimator.onAnimationUpdate(),这个方法也就是业务测实现动画逻辑的地方。
执行动画时,Choreographer回调到AnimationHandler$Choreographer.FrameCallback.doFrame():1
2
3
4
5
6
7
8
9
10
11AnimationHandler.java
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
动画完成后,执行Choreographer.scheduleVsyncLocked()调度下一个Vsync(“调度Vsync”部分会再讲到),再次执行下一帧的动画流程。就这样循环,完成整个动画过程。使用View.postOnAnimation()流程也大致相同。可以借助profiler跟下代码。
那AnimationHandler中的Choreographer.FrameCallback是怎么注册到Choreographer中的呢?
答案是调用ValueAnimator.start()开始动画的时候。start之后会走到ValueAnimator.addAnimationCallback()1
2
3
4
5
6
7
8android.animation.ValueAnimator.java
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
然后执行到AnimationHandler.addAnimationFrameCallback()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19android.animation.AnimationHandler.java
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
// getProvider()就是AnimationHandler中的内部类MyFrameCallbackProvider
getProvider().postFrameCallback(mFrameCallback);
}
}
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
// 向Choreographer中注册FrameCallback,该callback会放到Choreographer的mCallbackQueues,当Choreographer.doFrame()执行doCallback()时就从Queue中找到该FrameCallback进行回调
mChoreographer.postFrameCallback(callback);
}
}
其中,getProvider().postFrameCallback()中的mFrameCallback就是Choreographer回调AnimationHandler的FrameCallback。postFrameCallback()会把该callback会放到Choreographer的mCallbackQueues,当Choreographer.doFrame()执行doCallback()时就从Queue中找到该FrameCallback进行回调1
2
3
4
5
6
7
8
9
10
11android.view.Choreographer.java
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
...
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
...
}
}
其他的动画方式也可以通过查看AS->profiler->CPU来看具体的方法调用栈,理解其实现思路。
TRAVERSAL调用栈
traversal过程主要是指View的measure、layout、draw。
像View.invalidate()、View.requestLayout()都会触发traversal流程。跟踪代码,会执行到下面:1
2
3
4
5
6
7
8
9
10
11
12
13android.view.ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// MessageQueue中设置同步屏障,优先处理异步消息,即后面会讲到的TraversalRunnable
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 将TraversalRunnable入队,
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
调度Traversal时,为了优先处理UI绘制操作,在消息队列中加入了同步屏障,且Traversal对应的Message设置为异步消息。这块知识点可以参考:Android 同步屏障机制(Sync Barrier)/#more](https://hningoba.github.io/2019/12/06/Android 同步屏障机制(Sync Barrier)/#more))
mChoreographer.postCallback()会执行到下面方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15android.view.Choreographer.java
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
...
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
// 如果不在当前线程,发送一个异步消息
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
上面得了流程会向底层调度Vsync信号。等下一个Vsync信号到来时,会执行doFrame()。进而在Choreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL)中,从CallbackQueue中找到Traversal对应的CallbackRecord执行。
从下图可以看到Choreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL)内部执行的是ViewRootImpl.TravasalRunnable。这一点上面也提到了。
TravasalRunnable.performTraversals()内部执行了大家比较熟悉的View绘制流程,即measure、layout、draw。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35android.view.ViewRootImpl.java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
...
performTraversals();
...
}
}
private void performTraversals() {
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
// 执行measure操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
if (didLayout) {
// 执行layout操作
performLayout(lp, mWidth, mHeight);
}
if (!cancelDraw && !newSurface) {
// 执行draw操作
performDraw();
}
}
调度Vsync
上面讲Animation调用栈时提到每一帧动画结束后都向底层主动调度下一个Vsync,用来做下一帧的渲染工作。具体调度代码在下面:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31android.view.Choreographer.java
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
// 调度Vsync标记位
mFrameScheduled = true;
// 默认使用Vsync
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
// 如果在当前线程,直接调度Vsync,否则通过Handler发消息调度
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
...
}
}
}
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
最后通过DisplayEventReceiver向JNI申请Vsync信号。1
2
3
4
5
6android.view.DisplayEventReceiver.java
public void scheduleVsync() {
...
nativeScheduleVsync(mReceiverPtr);
}
JNI层调度Vsync方法:1
2
3
4
5
6
7
8
9android_view_DisplayEventReceiver.cpp
static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
sp receiver =
reinterpret_cast(receiverPtr);
// 调用NativeDisplayEventDispatcher.scheduleVsync()
status_t status = receiver->scheduleVsync();
...
}
NativeDisplayEventDispatcher继承自DisplayEventDispatcher,scheduleVsync()本质上是调用DisplayEventDispatcher:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16DisplayEventDispatcher.cpp
status_t DisplayEventDispatcher::scheduleVsync() {
if (!mWaitingForVsync) {
...
// 调用DisplayEventReceiver.requestNextVsync(),请求SurfaceFlinger发送Vsync
status_t status = mReceiver.requestNextVsync();
if (status) {
ALOGW("Failed to request next vsync, status=%d", status);
return status;
}
mWaitingForVsync = true;
}
return OK;
}
当下一个Vsync信号到来时,JNI会回调DisplayEventReceiver.dispatchVsync(),然后执行FrameDisplayEventReceiver.onVsync(),这部分内容上面已经讲到。1
2
3
4
5
6
7android.view.DisplayEventReceiver.java
// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
利用FrameCallback计算帧率
有些APM计算帧率的方式和处理动画的逻辑类似,利用FrameCallback的回调,在doFrame()中计算帧率。1
2
3
4
5
6
7
8
9// 注册自定义FrameCallback
Choreographer.getInstance().postFrameCallback(APM.mFrameCallback);
// 在回调中计算帧率
val APM.mFrameCallback = Choreographer.FrameCallback() {
fun doFrame(long frameTimeNanos) {
frameCounter ++
}
}
开始和结束监听帧率时记录start和end时间戳,结束时通过FrameCallback中的帧数就可以计算帧率。
总结Choreographer收到显示子系统发送的定时脉冲(Vsync)后,协调下一帧的animations、input、drawing工作。
收到Vsync后会响应Choreographer.doFrame(),每一帧的INPUT、ANIMATION、TRAVERSAL都是从这个方法开始执行的。
Choreographer是线程私有的,每个Looper有自己的Choreographer实例,跨线程调度任务通过post callback方式。
计算帧率可以借鉴动画的实现思路,自定义FrameCallback。
App没有更新操作,比如没有需要执行的动画、用户没有交互等,app层是不会渲染的。因为每一帧的操作都需要先向SurfaceFlinger调度Vsync。比如View.invalidate()、View.requestLayout()、View.postOnAnimation()、ValueAnimator.start()等都会触发Vsync调度。
参考