invalidate 三部曲之历经 Choreographer

Choreographer,编舞者。

Choreographyer 三部曲


1
2
3
4
5
6
7
8
9
10
11
12
13
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

上一期讲到了这里,也就是scheduleTraversals()真正核心的内容,只是加入了 Choreographer 的回调队列。那么这个回调队列是怎么一回事?invalidate()之后不是触发 View 刷新了吗?加入回调队列和这个有什么关系?

这一篇文章,我们就来深入了解 Choreographer 这个类。

写在前边

Choreographer 负责连接 VSYNC 信号和 framework 层绘制。

从这篇文章为什么是 VSYNC 我们知道, VSYNC 信号是一种刷新机制,会根据显示器的频率定时触发,通知CPU处理下一帧的绘制准备工作。

另一方面,Android 显示系统是由 Window 及其内部的 View 构成,显示的内容需要各个 Window 控件自己定义绘制,那么显示系统什么时候该去处理绘制的工作呢?当然最合适的时机是 VSYNC 信号触发,而 Choreographer 就是这二者之间的中介。显示系统想要更新内容的时候,就通过 Choreographer 注册一个回调,等 VSYNC 信号来临时,就做一次更新,然后继续注册回调监听下一次,直到没有需要更新的内容。

另外,Android 为了系统的流畅度,又对绘制的内容分类,优先响应输入事件,然后是动画,接下来才是绘制,最后做一些收尾工作,这个绘制内容的分类就体现在内部维护的 4 个回调队列中,等 VSYNC 事件来临,按序响应。

整个 Choreographer 就做了这么多事情,接下来我们按事件流顺序讨论

全部流程请参考序中的时序图:invalidate 三部曲序

从 scheduleTraversals() 开始讲起

scheduleTraversals() 会调用 postCallBack()

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
35
36
37
38
39
40
41
/**
* Posts a callback to run on the next frame.
* <p>
* The callback runs once then is automatically removed.
* </p>
*
* @param callbackType The callback type.
* @param action The callback action to run during the next frame.
* @param token The callback token, or null if none.
*
* @see #removeCallbacks
* @hide
*/
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}

public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) {
......

postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}

private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long 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);
}
}
}

这里最后会将 Runnable 类型的 action 加入到 callbackType 所属队列,callbackType 的值为 CALLBACK_TRAVERSAL。

Choreographer 总共定义了四个类型的回调队列 CallbackQueue,单向列表,在 Choreographer 初始化的时候就构造好了,然后会根据优先级来处理这四个回调队列中的内容,我们后续详解。

然后如果请求执行时间超过当前系统时间,会立即触发,否则通过 hanlder 延时触发

注册回调

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
35
36
37
38
39
40
41
42
43
44
45
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) { // 如果使用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 { // 不使用VSYNC,那么用自定义的定时操作触发,可用于调试
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);
}
}
}

private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync(); // 注册一个VSYNC监听者
}

/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
public void scheduleVsync() {
......

nativeScheduleVsync(mReceiverPtr);

}

立即触发的内容主要是使用本地方法去注册了一个回调,当然如果之前注册过了,这里什么都不会做。另外这里也提供了 USE_VSYNC 变量决定是通过 VSYNC 触发,还是使用 Handler 定时触发,方便调试

有注册当然就有回调,在 DisplayEventReceiver 这个类中,我们找到了从 native 回调回来的方法

1
2
3
4
5
// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}

onVsync 方法的具体实现是在子类中,传说中的模板方法设计模式,DisplayEventReceiver 只有一个实现类 FrameDisplayEventReceiver

接收回调

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
35
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信号发生时回调
......

// 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.
// 使用Handler来保证vsync信号依次按序执行,防止因为vsync信号,导致整个队列饿死,
// 两个问题:1. 为啥要用Handler?而不是直接调用执行
// 2. prevent这句是什么意思?
......

Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}

这里通过 Handler 的方式将自己发送出去执行 doFrame(),而不是通过直接调用的方式,主要是为了解决不同线程中的同步问题,这里是通过 Message 的 setAsynchronous() 方法实现。有时间会通过解析 Handler 机制来分析这个方法。

doFrame

兜兜转转,终于来到我们今天的主角。如果你使用 systrace 分析过 Android 性能问题,那么你一定不会对这个函数感到陌生,这就是整个 input, animation, 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
void doFrame(long frameTimeNanos, int frame) {
......

doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
......
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
......
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);

......
}

void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
......

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(frameTimeNanos);
}
......
}

这四个 doCallbacks 所做的就是处理之前我们通过 postCallback() 传入的回调,这里会根据不同的类型,按序处理,通过这段代码,我们可以看到执行的优先级是

  1. CALLBACK_INPUT,输入事件
  2. CALLBACK_ANIMATION,动画
  3. CALLBACK_TRAVERSAL,绘制
  4. CALLBACK_COMMIT,收尾处理

也就是我们文章开头所说的绘制类型。

结束

还记得我们调用 invalidate() 加入的回调吗?
在这里

1
2
3
4
5
6
7
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

这个回调是 ViewRootImpl 自己定义的,也就是说 Choreographyer 只是帮忙居中协调,规范了 ViewRootImpl 的执行时间罢了,具体的内容还需要靠 ViewRootImpl 自己,
好,这一篇我们就分析到这里,已经将 Choreographyer 摸了个透,
之前神神秘秘的类,其实内容就是这么简单
很多时候,恐惧来源于未知,知道了,也就没有那么可怕了。

预知

view 树真实调度发生了什么?
所谓的硬件加速到底是什么?

请看下回分解

invalidate 三部曲之历经 Choreographer

http://www.0xforee.top/2018/12/18/invalidate-of-choreographer/

作者

0xforee

发布于

2018-12-18

更新于

2023-08-19

许可协议


欢迎关注我的公众号 0xforee,第一时间获取更多有价值的思考

评论