一、引言
本篇文章主要讨论 EventThread 的框架,围绕客户端请求 VSync Event 的过程展开讨论,使用图像来表达,尽量避免过多的细节实现和代码。本文基于 Android 14 进行讨论。
阅读本文前,需要先了解一些关于 Vsync (垂直同步)信号的知识。
二、事件(Event)
1.1、都有哪些事件?
我们这里讲述的事件(event)指的是与显示(display)相关的事件,通过结构体进行定义。主要有四种:
1.2、一个 Event 长什么样子?
和一般的通信协议类似,这里的一个 Event 由一个结构体 Header 和一个联合体组成。Header 用于表示 Event 的通用信息;联合体用于表示具体的事件。
1.3、VSync 事件
本文主要围绕客户端请求 VSync Event 进行学习。这里先简单介绍一下 VSync 事件。
客户端接收到 Vsync 信号来做渲染工作。关于 Vsync 信号的相关信息则是被封装为一个 VSync Event 从 SurfaceFlinger 端发送给客户端的。
对于 VSync Event,其联合体的内容实际上也是一个结构体 VSync
。其内部由一个 32 位非负整型和一个用于表示 Vsync 信息的结构体 VsyncEventData
组成,其包含了关于 Vsync 信号的详细信息,包括当前 Vsync 信号产生时的 Vsync 周期、Vsync 信号的 id、Vsync 信号时间戳等等。
注意:VSync 事件需要先请求,然后 Vsync 调度模块才会派发给 EventThread,进而再派发给客户端,否则不会产生 VSync 事件。可以通过调用 setVSyncRate()
或者 requestNextVsync()
来请求,关于这个方法下文有详细介绍。
1.4、其它事件
如果想要接收 ModeChanged 和 FrameRateOverrides 两种事件,需要在注册 EventThreadConnection 时传入相关标识。
其它事件,目前也就剩下 HotPlug(热插拔)事件,则是立即派发。
三、事件(event)分发(dispatch)

1、EventThread
1.1、创建 EventThread
作为基础服务的 SurfaceFlinger,这些的工作必然是在开机初始化阶段就要完成的。
在初始化 SurfaceFlinger 时,创建了两个子线程(EventThread)用来向客户端派发显示事件,如 VSync Event。根据枚举类 Cycle 提供的两个选项进行创建。
- Render :Surface rendering 绘制图像的过程。 将会以此创建一个名为 app 的 EventThread,负责分发 Vsync-app 信号,给客户端触发渲染工作,应用据此执行渲染(render)工作。
- LastComposite :也是绘制图像,不过这个触发时间点比 Vsync-sf 提前一个刷新周期。 将会以此创建一个名为 appsf 的 EventThread,分发 Vsync-appsf 信号给客户端触发渲染工作。假设渲染工作及时做完,SurfaceFlinger 将在下一个 Vsync-sf 到来时进行合成。
cpp
std::make_unique<impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf", ...
为了方便识别 EventThread,使用 ConnectionHandle 来进行标识,分别是 mAppConnectionHandle
和 mSfConnectionHandle
。
这两个 EventThread 除了名字、handle id 不同外,最重要的区别是 workDuration
和 readyDuration
两个变量表示的时间间隔不同。关于这两个量,决定了 Vsync-app 和 Vsync-appsf 分别与 Vsync-sf 的间隔时长,将在其它的文章再分析。
1.2、注册到 VsyncSchedule 中
发送给客户端的 Vsync 信号是从 VsyncSchedule 中派发出来的,VsyncSchedule 主要由 VsyncController、VSyncDispatch、VSyncTracker 三个子模块组成。VSyncDispatch 模块负责派发 Vsync 信号。EventThread 向 VsyncDispatch 中注册自身,以及一个回调函数。VSyncDispatch 派发 Vsync 信号时,遍历注册的 EventThread,一一调用它们所注册的回调函数。
具体而言,EventThread 与 VSyncDispatch 打交道是通过依赖成员对象 VSyncCallbackRegistration 实现的 。为了降低两者之间的耦合性,EventThread 通过 Lambda 表达式将 onVsync()
封装为一个匿名函数对象传给 VSyncCallbackRegistration。VSyncCallbackRegistration 持有对 VSyncDispatch 的引用,调用 VSyncDispatch 提供的注册方法,将包含 onVsync() 的匿名函数对象封存在一个 VSyncDispatchTimerQueueEntry 对象中。
VSyncDispatch 的具体实现是 VSyncDispatchTimerQueue,其维护了一个名为 mCallbacks 的容器。mCallbacks 中的元素是由一个整型值 token 和一个 VSyncDispatchTimerQueueEntry 对象所组成键值对。从上面的分析可知,每一个 EventThread 都应一个 VSyncDispatchTimerQueueEntry。除此之外,SurfaceFlinger 自己的 MessageQueue 也对应有一个。因此,在目前的系统中,mCallbacks 的元素个数一般是 3 个。
可以发现,这像是一个观察者设计模式。VSyncDispatch 作为被观察对象,也即"目标",其自身维护有一个包含观察者信息的容器。每当有 Vsync 信号需要派发时,会通过观察者提供的回调函数来通知观察者进行响应。区别于一般的广播方式,VSyncDispatch 是有根据的针对性通知。其根据的是两个 Duration 的值,这部分内容本文不做深入分析。
1.3、threadMain 里的 while 大循环
在 EventThread 的构造函数中,通过 std::thread 库创建了一个新的线程,使用 lambda 表达式传入一个匿名函数对象,其通过 this 指针获得了访问当前对象的成员变量和成员函数。
EventThread 使用状态机(State Machine)来处理事件循环。
- Idle:表示当前处于空闲状态,等待客户端注册或请求 Vsync 信号;
- Quit:退出循环,EventThread 被析构掉时。
- SyntheticVSync:表示由 EventThread 来提供虚假的 Vsync 信号(灭屏、虚拟屏时会是这种状态,以 60Hz 的速度提供)。
- VSync:表示正常亮屏时请求 Vsync 信号。
通常情况下:
- 当没有 Vsync 请求时,状态机进入 Idle 状态,循环体阻塞在 wait 方法处。
- 当连续请求或配置周期性请求 Vsync 时,在 Vsync 信号到来前,状态机处于 VSync 状态,循环体阻塞在 wait_for 方法处。等待超时时长为 1 秒。若超时,则说明有异常了。超时后产生一个虚假的 Vsync 信号派发给客户端。这个可以让客户端重新有机会再次发出请求,以跳出异常环境,祈求自动回复。
- 灭屏、虚拟屏等场景下,客户端仍在请求 Vsync,状态机进入 SyntheticVSync 状态,EventThread 不会通过 VSyncCallbackRegistration 向 VsyncScheduler 请求 Vsync 信号,而是自己产生周期为 16ms 的虚假的 Vsync 信号(即等待超时时长为 16ms),封装为事件派发给客户端。在这期间,循环体也是阻塞在 wait_for 方法处。

1.4、threadMain 中如何处理 Event
下图表达了大致流程。从待处理事件队列中取出一个事件(我们仅讨论 VSync Event,没有其它事件的处理比其更复杂了),遍历向 EventThread 注册过的、尚且有效的连接器,一一去判断是否需要消费该事件。若需要,连接器则加入到消费者数组 consumers 中。所有连接器都遍历过后,消费者数组中连接器逐一向其客户端进行事件派发。
更多细节可以阅读第 3、5 小节的内容。
2、EventThreadConnection

2.1、系统初始化时创建的 EventThreadConnection
系统初始化时,在创建完 EventThread 后,还顺带创建了一个 EventThreadConnection。他们都被 Scheduler 所管控,被与 ConnectionHandle 关联一起,扔在了无序图容器 mConnections
中。 (有一说一,这个容器的命名不应该叫 mEventThread 才合理吗?)
EventThreadConnection,自然归 EventThread 所管控,纯只有连接器的数组,叫 mDisplayEventConnections
倒是非常容易理解。
还有,顺带创建的 EventThreadConnection 似乎、好像、可能、的确是没有实际使用的,仅仅用于测试。关于其自身的属性、功能,请看下面。
2.2、客户端为请求 VSync Event 而创建的 EventThreadConnection
客户端通过 SurfaceFlinger 提供的接口,创建 EventThreadConnection 并注册到 EventThread 中。
(1)怎么管理
SurfaceFlinger 为客户端提供了两种 VSync Event。客户端需要哪种,在建立连接时,就需要明确是哪一种。通过 VsyncSource 来明确:
- eVsyncSourceApp 对应 Vsync-app;
- eVsyncSourceSurfaceFlinger 对应 Vsync-appsf。 这样,客户端所请求创建的 EventThreadConnection 对象,将会根据对应的 ConnectionHandle 而被扔到对应 EventThread 的连接器数组中去。
(2)功能
① 连接器
创建 BitTube,建立全双工通信通道。
- 发送端是 SurfaceFlinger 在使用,主要用来写入事件通知给相应的"观察者"。
- 接收端则提供了
stealReceiveChannel()
方法供客户端获取这个 BitTube。注意这个方法的名称,steal,偷取,有意思。
② 重新同步 Hardware Vsync
还有一个功能:请求重新同步硬件 Vsync 信号。为了避免 Software Vsync Model 产生的累积误差,SurfaceFlinger 提供了一个策略,即每隔 750ms(硬编码),就打开【允许打开硬件 Vsync 同步】的开关。到底会不会真的打开硬件 Vsync 信号的开关并接收,这里面还有其它的判断,本文不深入分析。
具体实现是:Scheduler 提供了一个封装有 resync()
方法的匿名函数对象,在请求创建 EventThreadConnection 时,注册到其中的。每当客户端请求 Vsync 信号时,将会触发一次调用,届时会检查距离上一次请求 Vsync 信号时有没有超过 750ms。
3、Make VSync Event
本小节重点关注一下 VSync Event 是何时创建、如何传输给客户端、怎么被使用的。
3.1、VSync 事件何时产生?
我们知道客户端收到的 Vsync 信号实际上都是 Software Vsync Model 计算出来的,那如何让 Vsync Model 为我们产生 Vsync 信号呢?这需要我们主动触发。比较尴尬,这里需要引用第 5 小节开头的图来说明。
当客户端请求 Vsync 信号时,一方面会通知条件变量,唤醒 EventThread 开始 threadMain 的循环体逻辑(如果原先也没有阻塞等待,也没区别);另一方面,也会将客户端关联的 EventThreadConnection 的 VsyncRequest 修改为 Single(关于 VsyncRequest 详见 5.2 小节)。于是,在新的一次循环体逻辑中,EventThread 的状态机 mState 获得 VSync 状态,将会触发调用 VSyncCallbackRegistration::schedule
方法。
我们在 1.2 小节中分析过,VSyncCallbackRegistration 包含对 VSyncDispatch 的引用,用于与 VSyncDispatch 进行交互,如提供的 schedule()
方法,用于请求调度器派发 Vsync 信号;cancel()
方法,用于取消接收 Vsync 信号等。关于 Vsync 调度模块如何计算 Vsync 信号的内容这里不做深入分析,简而言之,其计算出下一帧的 Vsync 时间戳后,会设定到 SurfaceFlinger 中的 Vsync 计时器(TimeKeeper)中。
当计时结束后,将会重置计时器,并触发对应的回调函数。这个回调函数是客户端在创建 EventThread 时传入的一个 Lambda 匿名函数,其内部调用了 EventThread::onVsync()
方法。VSync Event 就是在其中被创建的。创建完成后,就被保存到 EventThread 持有的待处理事件队列 (mPendingEvents
)中,然后在 EventThread 的 while 大循环中被拿出来消费掉。
3.2、VSync 事件如何传输给客户端?
EventThread 初始化完成后,就会进入 while 大循环中。循环主体逻辑通过状态机进行判断。
没有事件需要处理时,则进入空闲状态,阻塞等待新事件。当有新事件时,则会再次唤醒循环,将其加入到 mPendingEvents
中时,通过条件变量(mCondition
)唤醒 while 大循环。循环体中从 mPendingEvents
中取出事件,判断是否需要消费。若不需要,则直接被丢弃。
这里假设 VSync Event 将会被消费(实际是否消费,还需要考虑连接器的 Vsync 请求状态)。所谓消费,也就是将该 VSync Event 派发(dispatch)给请求 Vsync 信号的客户端。
Event 的派发是通过调用客户端这一侧的显示事件接收者 (DisplayEventReceiver)提供的静态方法 sendEvents()
,具体是通过 BitTube 来进行传输的。BitTube 是通过封装了一个 socketpair 来实现进程间通信的。socketpair 支持全双工通信,在数据发送端设置有数据缓冲区。
3.3、VSync 事件如何被使用?
显示事件派发者 (DisplayEventDispatcher)继承 LooperCallback 并重写 handleEvent()
,通过 epoll 机制监听 socketFd 来接收 Event。当 SurfaceFlinger 端通过 BitTube 写入 Event 时,epoll 机制监听到有事件写入,调用 handleEvent()
进行处理。
继而调用 getEvents()
,从 socketPair 中读取 Event,然后根据 Event 的 Header 信息判断是何种类型的事件,进入不同的处理分支。如果是 VSync Event,该方法会返回 true,继而调用 dispatchVsync()
方法。这是个接口性质的方法(纯虚函数),需要客户端去重写实现的~
4、DisplayEvent Dispatcher & Receiver

4.1、Dispatcher & Receiver
DisplayEventDispatcher 和 DisplayEventReceiver 下文分别简称为 Dispatcher 和 Receiver。
客户端为了请求和接收事件,需要包含 Dispatcher。
Dispatcher 包含一个 Receiver。他们是一个整体,但各司其职,相辅相成。Dispatcher 主要负责向客户端派发事件、提供请求 VSync 事件的接口,不直接与 EventThread 端打交道;Receiver 则主要负责与 EventThread 进行交互,包括接收事件、请求 VSync 事件,这些从他们的命名上也可以观之一二。本小节重点讨论客户端是如何申请注册接收事件。派发事件的过程我们在本章 3.3 小节中已经讨论过。
Dispatcher 作为事件的派发者,提供了一些方法,用以响应处理不同的显示事件。从 BitTube 中读取事件时,会根据事件头(Header)判断事件类型,从而调用相应的处理方法。这些方法定义为纯虚函数,需要客户端继承 Dispatcher 并实现。
Receiver 持有一个 IDisplayEventConnection 引用,以此与 EventThread 端进行交互。这个 IBinder 是在创建 Dispatcher 时,也即创建 Receiver 时,通过获取 SurfaceFlinger 服务而申请创建的一个 EventThreadConnection 对象。关于 EventThreadConnection 我们在本章 2.2 小节中已讨论。需要注意的是,BitTube 对象实际持有者也是 EventThreadConnection,Receiver 通过其提供的 stealReceiveChannel
方法拿到仅仅是一个引用。
Dispatcher 还提供了请求 VSync 事件的方法,请求的主要工作是由 Receiver 完成的。具体内容我们在第 5 节中进行讨论。
4.2、Choreographer
Choreographer 是源码提供的一个类。其继承并实现了 DisplayEventDispatcher 提供的纯虚函数。其主要是面向 Java 层应用提供的。精力有限,暂未研究,不过迟早要学啊。
如果是在 Native 层,完全可以自己写一个类直接继承 DisplayEventDispatcher 并实现纯虚函数,想加什么功能加什么功能。Choreographer 是一个非常好的学习案例。
5、VSync Event 请求&派发 机制
这一节的图是真的难画。下图是对第三章图中 threadMain 部分的详细表达。
5.1、VSyncState
为了理解下文中的一个小点,我们需要知道一个事实,即运行 Android 系统的显示设备在开机初始化阶段会收到驱动发送过来的一个热插拔事件,也就是显示硬件连接上了。在软件层面上,为了方便通过显示驱动与显示硬件交互,需要据此创建一个 DisplayDevice 对象。要知道合成与显示是强相关的呀,有了 DisplayDevice 才能继续后续的初始化工作。
不过,这种强耦合的编码方式在 Android 14 中有所改善,开始逐步将 Scheduler 与显示设备解耦。本文不深入研究这方面了。只需要了解驱动通知过来的热插拔事件与 PhysicalDisplayId 关联在一起,形成一个 EventThread 中定义的 Event,被分别加入到两个 EventThread 的待处理事件队列中,并将在各自的 threadMain 中处理这个事件。
于是,EventThread 所持有的内部子类 VSyncState 对象诞生了~
话说,EventThread 里处理热插拔事件,也就仅仅为了管理 VSyncState 而已。换句话说,是插 还是拔这也决定了是否还需要 EventThread 了。
VSyncState,顾名思义,其保存了所属 EventThread 对应物理显示设备Id、连接到显示设备后收到的 VSync Event 的数量,以及当前是否产生"伪 Vsync 信号"------其主要用于灭屏、虚拟屏。
5.2、VSyncRequest
(本节图画完后,根本不想写任何文字了)
需要了解到一个事实:Vsync 信号并不是主动派发的,而是需求者自己主动请求后,才会接受到 Vsync 信号。在默认且正常的情况下,客户端请求一次,VsyncScheduler 才会派发一次。而 VSyncRequest 正是用于描述客户端是否在请求 Vsync 信号。
VSyncRequest,是一个枚举类,有四种值:
- None = -2:不请求。
- Single = -1:唤醒接下来的两帧,以避免调度程序开销。
- SingleSuppressCallback = 0:仅唤醒下一帧。
- Periodic = 1:周期性的自动请求 Vsync 信号。
是不是没看懂源码中关于 Single 和 SingleSuppressCallback 注释?按照变量的命名原则,Single 不是理解为单次吗?咋是唤醒接下来的两帧呢?啥意思呢?
Single 状态时,EventThread 最终的确只是派发一次 Vsync 信号。但是:
- 通常情况下,客户端也不会只请求一次。你说请求一次能干嘛?闪屏玩?还只闪一次?
- 也不能因为客户端请求一次后,就连续主动地像客户端周期性的派发吧,谁知道客户端还要不要了。
- 又考虑到,如果每申请一次,仅触发 Vsync 调度器去计算一个 Vsync 信号。在连续请求 Vsync 信号的场景下,每次都需要通过条件变量去唤醒循环,这无疑也是一个较大的开销。
因此,源码中关于 Single 和 SingleSuppressCallback 的注释,实际上说的是一种优化。具体优化策略描述如下:
- 增加一种状态,名为 SingleSuppressCallback。
- 第一次请求时,状态切换为 Single,唤醒循环体后将会立即触发 schedule,然后循环体在 wait_for 处阻塞等待;
- 第一个 Vsync 信号到来时,封装为 Event,唤醒循环体进行处理,状态切换为 SingleSuppressCallback,此事件可以被消费。状态机 mState 仍然为 VSync ,将继续触发 schedule,再次在 wait_for 处阻塞等待;
- 客户端连续请求,第二次请求时,状态切换为 Single,唤醒循环体后将会立即触发 schedule,然后循环体在 wait_for 处阻塞等待;
- 第二个 Vsync 信号到来时,封装为 Event,唤醒循环体进行处理,状态切换为 SingleSuppressCallback,此事件可以被消费。状态机 mState 仍然为 VSync ,将继续触发 schedule,再次在 wait_for 处阻塞等待;
- 客户端连续请求,第三次请求时.......如此循环。
- 客户端连续请求,第 N 次请求时.......
- 第 N 个 Vsync 信号到来时,封装为 Event,唤醒循环体进行处理,状态切换为 SingleSuppressCallback,此事件可以被消费。状态机 mState 仍然为 VSync ,将继续触发 schedule,再次在 wait_for 处阻塞等待;
- 客户端不存在第 N+1 次。由于处理第 N 个 Vsync 信号时,也触发了 schedule,我们将会接收到第 N+1 个 Vsync 信号。当第 N+1 个 Vsync 信号到来时,同样地封装为 Event 进行处理,但是 VSyncRequest 状态被切换为 None,此事件不可以被消费 。状态机 mState 被判定为 Idle ,此时将会触发 cancel,其目的是取消可能还在计时尚未派发到此的 Vsync 信号。
分析到这里,"唤醒接下来的两帧,以避免调度程序开销 "、"仅唤醒下一帧",这两句注释似乎也就能理解了。
在第四章的 2.1 小节,讨论了一种因为客户端的 Vsync 请求没有及时得到响应而导致丢帧的问题,也是理解这一个知识点的绝佳案例。
5.3、一般情况下 VSync Event 请求&派发
这一小节其实就是上面刚讨论的"优化策略 1~8 步",不再赘述。
5.4、二般情况下 setVsyncRate
这个方法的名字容易让新手产生疑惑。VsyncRate 和 VsyncPeriod 是什么关系?
这个方法实际上就是配置 VSyncRequest 的值。在 IDisplayEventConnection.aidl 提供的注释中:
- 当设置为 1 时,每个 VSync Event 都会被派发。 也即配置 VSyncRequest = Periodic,一旦开始请求,就会周期性、不停歇地的请求并接收 Vsync 信号。
- 当设置为 2 时,每隔一个事件派发一次,依此类推。 实际上,设置为大于 1 的整数都是可以的,设置为 n(n > 1),那将会每隔 n-1 个 VSync Event 才会派发一次。
- 当设置为 0 时,除非已调用
requestNextVsync()
,否则不派发任何事件。 也即配置 VSyncRequest = SingleSuppressCallback,根据我们在 5.2 中的分析,这个时候不会向客户端派发 VSync Event。如注释所说,只有在该状态下,客户端通过调用requestNextVsync()
,EventThread 才会向客户端派发一次,也仅仅是一次而已。想要继续派发,需要客户端在此调用requestNextVsync()
。
注意,这个方法仅接受大于等于 0 的正整数。
四、实践
1、工作正常
我们可以在下面这一份 systrace 上看到👇: 【线程 systemui 2907】请求 Vsync-app 来触发绘制。 【线程 1981】请求了 Vsync-appsf 来触发绘制,并在一个 Vsync 周期后通过 SF 进行合成。
2、工作异常
正常工作一个样,异常工作千千万~但是由于工作经验不足,目前就讨论一条吧。
2.1、SF binder 线程 runnable 时长超过一个 Vsync 周期时会...
会丢帧。
这个案例也是我想写第三章第 5.2 小节的初衷,进而写了全篇文章。😅
让我们来分析造成丢帧的原因和过程吧。
(1)原因
CPU 资源紧张,高优先级线程太多,进一步造成资源争抢。SurfaceFlinger 的一个 Binder 线程很不幸地没能及时争抢到 CPU 时间片,导致 Runnable 时间过长,进而使得客户端(线程 3071)的 Vsync 请求没有得到及时处理。
(2)过程
注:线程 3071 注册在 appSf-EventThread 中,接收 Vsync-appsf 信号。
下面序号对应上图序号。
①:appSf-EventThread 分发 Vsync 信号,VSyncRequest 变为 SingleSuppressCallback,线程 3071 收到后开始做相关工作;
②:线程 3071 相关工作结束后,再次请求 Vsync 信号,并及时被 SurfaceFlinger 响应,VSyncRequest 变为 Single;
③:一切正常,重复步骤 ①,VSyncRequest 变为 SingleSuppressCallback;
④:线程 3071 相关工作结束后,再次请求 Vsync 信号;
⑤:问题出现:SurfaceFlinger Binder 线程 Runnable 时间过长,导致步骤 ④ 的 Vsync 请求没有得到响应;
⑥:由于 Vsync 请求没被及时响应,VSyncRequest 仍为 SingleSuppressCallback ,没有按照期望变为 Single。此时经过 shouldConsumeEvent()
判断后,VSyncRequest 被赋值为 None ,并返回 false,导致该 VSync Event 不会被消费。EventThread 状态机 mState 也会随之进入空闲(Idle)状态。
⑦:此时,步骤 ④ 的请求终于被响应了,VSyncRequest 再次变为 Single。appSf-EventThread 分发 Vsync 信号后,VSyncRequest 又再次变为 SingleSuppressCallback,线程 3071 收到后开始做相关工作。