1. 前言
忽然有一天,我想要做一件事:去代码中去验证那些曾经被"灌输"的理论。
-- 服装学院的IT男
本篇为 VSync 系列的第四篇,上一篇介绍了一个应用是如何把自己的链接添加到 "app" 这个 EventThread
下的 mDisplayEventConnections
中的。
本篇分析应用端是如何请求 VSync
并接收的。
本系列为之前学习 SurfaceFlinger 整理的一些笔记,现在分享出来,希望能帮助到有需要的同学。代码基于 Android 13,虽然很多逻辑与最新源码有所不同,但总体思路依然不变,不影响对 VSync 整体逻辑的理解。
VSync 系列目录:
应用层请求 VSync 一般执行的是 Choreographer::postCallback ,比如在 ViewRootImpl::scheduleTraversals 方法下的这种调用:
csharp
# ViewRootImpl.java
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
下面开始分析:
1. Java层请求 VSync
java
# Choreographer
public void postCallback(int callbackType, Runnable action, Object token) {
// 没有延迟,则第4个参数为0
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis, new Throwable());
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
// 无延迟走这
scheduleFrameLocked(now);
} else {
// 需要延迟则通过Handler处理
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
最终进入 Choreographer::scheduleFrameLocked 方法。
csharp
# Choreographer
private final FrameDisplayEventReceiver mDisplayEventReceiver;
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
// 默认true
if (USE_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 {
......
}
} else {
......
}
}
}
private void scheduleVsyncLocked() {
try {
// 还加了trace
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
// 主流程
mDisplayEventReceiver.scheduleVsync();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
......
}
}
流程来到 FrameDisplayEventReceiver
, 它是 Choreographer
的内部类, 又是 DisplayEventReceiver
的子类,这里调用的 scheduleVsync
方法定义在父类中。
csharp
# DisplayEventReceiver.java
// 当前对象在 native 的指针
private long mReceiverPtr;
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
1.1 堆栈
Java
层的调用比较简单,调用链如下:
arduino
Choreographer::postCallback
Choreographer::postCallbackDelayed
Choreographer::postCallbackDelayedInternal
Choreographer::scheduleFrameLocked
Choreographer::scheduleVsyncLocked
DisplayEventReceiver::scheduleVsync
DisplayEventReceiver::nativeScheduleVsync -- 进入native层
核心流程在后面的 Native
层。
2. native 层请求VSync
对应的 JNI
文件为 android_view_DisplayEventReceiver.cpp
scss
# android_view_DisplayEventReceiver.cpp
static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
// 根据指针拿到native层的 receiver 对象
sp<NativeDisplayEventReceiver> receiver =
reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
// 申请Vsync
status_t status = receiver->scheduleVsync();
......
}
之前说过,这里的 NativeDisplayEventReceiver
父类是 DisplayEventDispatcher
,scheduleVsync
方法也是定义在父类中的。
scss
# DisplayEventDispatcher.cpp
status_t DisplayEventDispatcher::scheduleVsync() {
if (!mWaitingForVsync) {
ALOGV("dispatcher %p ~ Scheduling vsync.", this);
......
// 申请
status_t status = mReceiver.requestNextVsync();
if (status) {
ALOGW("Failed to request next vsync, status=%d", status);
return status;
}
mWaitingForVsync = true;
mLastScheduleVsyncTime = systemTime(SYSTEM_TIME_MONOTONIC);
}
return OK;
}
这里的 mReceiver
是 DisplayEventReceiver
对象。 DisplayEventReceiver
是在构造 DisplayEventDispatcher
时创建的。
scss
# DisplayEventReceiver.cpp
status_t DisplayEventReceiver::requestNextVsync() {
if (mEventConnection != nullptr) {
// 属性的APP 链接
mEventConnection->requestNextVsync();
return NO_ERROR;
}
return mInitError.has_value() ? mInitError.value() : NO_INIT;
}
这里的 mEventConnection
在应用连接 EventThread
时被创建,类型是 EventThreadConnection
,它代表着一个应用。
rust
# EventThread.cpp
// 无参
binder::Status EventThreadConnection::requestNextVsync() {
ATRACE_CALL();
ATRACE_BEGIN(((impl::EventThread*)mEventThread)->toNameString());
// 继续调用有参,传递自身链接
mEventThread->requestNextVsync(this);
......
}
// 继续调用
void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {
......
if (connection->vsyncRequest == VSyncRequest::None) {
// 设置为 Single
connection->vsyncRequest = VSyncRequest::Single;
// 并唤醒等待的线程
mCondition.notify_all();
} else if (connection->vsyncRequest == VSyncRequest::SingleSuppressCallback) {
// 设置为 Single,不需要唤醒
connection->vsyncRequest = VSyncRequest::Single;
}
}
流程来到 EventThread
。如果是首次请求,vsyncRequest
的状态肯定是 VSyncRequest::None
, 所以会执行第一个 if
分支,将 vsyncRequest
设置为 VSyncRequest::Single
并唤醒等待的线程。 这里的 connection
参数就是上一篇中应用添加到 mDisplayEventConnections
集合中的连接,用来区分是哪个应用发起的 VSync
请求。
这部分的调用链为:
arduino
DisplayEventDispatcher::scheduleVsync
DisplayEventReceiver::requestNextVsync
EventThreadConnection::requestNextVsync
EventThread::requestNextVsync
这里出现了一个类:VSyncRequest
,先来了解一下。
2.1 VSyncRequest
kotlin
# EventThread.h
enum class VSyncRequest {
None = -2,
// Single wakes up for the next two frames to avoid scheduler overhead
Single = -1,
// SingleSuppressCallback only wakes up for the next frame
SingleSuppressCallback = 0,
Periodic = 1,
// Subsequent values are periods.
};
class EventThreadConnection : public gui::BnDisplayEventConnection {
public:
......
// 请求类型
VSyncRequest vsyncRequest = VSyncRequest::None;
}
EventThreadConnection
代表一个应用请求接收 VSYNC-app
的连接,内部的 vsyncRequest
变量表示该应用对 VSYNC-app
的请求类型。
VSyncRequest
是一个枚举类:
None
: 默认状态,表示没有请求VSync
。Single
:会为该应用连接提供接下来两帧的VSync
信号。SingleSuppressCallback
:会为该应用连接提供接下来一帧的VSync
信号。Periodic
: 表示请求周期性的VSync
唤醒,适用于那些需要持续跟踪每一帧更新的场景。(暂时忽略)
应用 VSYNC-app
请求类型会按如下顺序转换: Single
-> SingleSuppressCallback
-> None
。 从上面的代码看到,如果应用持续申请,会在 SingleSuppressCallback
状态时切换为 Single
,所以完整的转换也可以理解成这样:
Single
-> SingleSuppressCallback
-> (Single
-> SingleSuppressCallback
) -> None
2.1.1 个人理解 VSyncRequest
SW-VSYNC
是按需产生的,对于单个应用也是如此。VSyncRequest
用于区分各个应用对 VSYNC-app
的请求类型。
这里举一个可能不太恰当的例子: 今年是龙年,很多龙宝宝。小兔崽子要喂饭,一天吃几顿,饿了就吃。 喂饭没有只喂一口的吧,第一口饭喂进去后,妈妈一般就会准备再舀一勺子准备为第二口。所以我理解成小孩子"啊"一张口的时候,妈妈最少要准备喂2次饭。(这个时候理解为VSyncRequest::Single 即:最少还要吃2口饭)。 小孩子也总有吃饱的时候,所以当小兔崽子吃完第一口后闭着嘴了,可是这个时候老妈勺子里已经有之前准备好的一勺子饭了,根据我的童年记忆,老妈肯定是要把这口饭塞进嘴里,然后这次喂饭才算结束的。 (所以我理解为这个时候的状态就是 VSyncRequest::SingleSuppressCallback 即:还要再吃一口饭) 喂饭结束后,小孩子吃饱了也不会再申请吃饭了,(这个时候就是 VSyncRequest::None 老妈也就可以去睡觉了(休眠状态))。
也就是说当小孩子主动张嘴"啊"申请要吃饭的时候,对老妈来说知道他饿了,则心里知道最少要喂2口(None - > Single),小孩子吃到第一口饭闭上嘴的时候,老妈会准备好下一口(Single - > SingleSuppressCallback)。
小孩吃完这口有两种可能:
-
- 没吃饱继续张口"啊",妈妈就知道还要准备2口(SingleSuppressCallback - > Single)
-
- 吃饱了,但是妈妈现在这勺子还得喂完才结束 (SingleSuppressCallback - > None)
这个例子可能不是很恰当,但是可以帮助建立一个初步的概念。实际上,应用请求 VSYNC-app
时的状态切换,还是以后面的代码分析为主。
2.2 EventThread::requestNextVsync 小结
回顾一下代码:
rust
# EventThread.cpp
void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {
......
if (connection->vsyncRequest == VSyncRequest::None) {
// 设置为 Single
connection->vsyncRequest = VSyncRequest::Single;
// 并唤醒等待的线程
mCondition.notify_all();
} else if (connection->vsyncRequest == VSyncRequest::SingleSuppressCallback) {
// 设置为 Single,不需要唤起
connection->vsyncRequest = VSyncRequest::Single;
}
}
现在对 VSyncRequest
有了个大概的概念,可以发现这段代码无论执行哪个分支,都会将 vsyncRequest
设置为 VSyncRequest::Single
。区别在于是否需要"唤醒线程"。
如果原来的状态是 VSyncRequest::None
,说明没有请求 VSYNC-app
,线程可能处于等待状态,需要唤醒。对应到例子里,就是小孩子饿了要吃饭,开始的时候妈妈在睡觉,需要先"唤醒"。而下面的情况则不需要唤醒。

3. VSYNC-app 核心处理
目前为止,我们只看到了应用发起请求,真正的逻辑还在后面。
这里唤醒了 "app" 线程,在构建 EventThread
的时候会创建这个线程,并执行 EventThread::threadMain
方法。
3.1 Vsync-app 线程处理状态
在看这个方法之前,先了解一下 EventThread
的几个执行状态:
c
# EventThread.h
enum class State {
Idle, // 空闲状态: 表示事件循环当前无特定任务执行。进入等待
Quit, // 退出状态: 事件循环准备结束
// 合成VSYNC状态: 在此状态下,系统可能因为某些原因(如无实际显示器连接)
// 而生成模拟的VSYNC信号,以维持程序逻辑的连续性。
// 暂时可忽略
SyntheticVSync,
VSync, // 当前正等待或已接收到真实的显示器VSYNC信号
};
// 当前状态
State mState GUARDED_BY(mMutex) = State::Idle;
有 4 个状态,正常情况下我们只需关注空闲状态 (Idle
) 和请求状态 (VSync
)。
3.2 EventThread::threadMain 方法
这个方法是
VSYNC-app
中的核心方法,代码量较多,逻辑也比较复杂,执行时的具体逻辑取决于当前的条件。 我目前还无法通过文字的形式让读者完全理解这部分逻辑。我自己在学习这部分内容时,花了一周时间,反复结合代码、日志、trace,并最终画出流程图,才算理解了这部分内容。 下面的代码中,我会尽量加上注释和解释,并附上整理好的流程图。
rust
# EventThread.cpp
void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
// 定义一个要消费 Vsync 事件的集合
DisplayEventConsumers consumers;
// 只要状态不是退出,则一直循环执行(没任务的时候内部会等待)
while (mState != State::Quit) {
std::optional<DisplayEventReceiver::Event> event;
// Determine next event to dispatch.
// 1. 确认是否有 Vsync 事件,有的话则赋值给event
// 如果有 Vsync 事件就会放在 mPendingEvents
if (!mPendingEvents.empty()) {
event = mPendingEvents.front();
mPendingEvents.pop_front();
// 这部分可忽略,正常情况不影响主流程
switch (event->header.type) {
......
}
}
// 是否有应用请求了 vsync
bool vsyncRequested = false;
// 2. 开始遍历mDisplayEventConnections的各个链接
// Find connections that should consume this event.
auto it = mDisplayEventConnections.begin();
while (it != mDisplayEventConnections.end()) {
if (const auto connection = it->promote()) {
// 只要应用链接的集合里有一个应用申请了, syncRequested 就为true
vsyncRequested |= connection->vsyncRequest != VSyncRequest::None;
// 是否要消耗event
if (event && shouldConsumeEvent(*event, connection)) {
// 消耗则添加到 consumers 集合
consumers.push_back(connection);
}
++it;
} else {
it = mDisplayEventConnections.erase(it);
}
}
if (!consumers.empty()) {
// 3. 如果有消费事件的应用,则派发时间到应用端
dispatchEvent(*event, consumers);
// 置空
consumers.clear();
}
{
// 4. 状态处理
// 定义当前线程的下一个状态
State nextState;
if (mVSyncState && vsyncRequested) {
// 4.1 有应用申请的话则 nextState = State::VSync
nextState = mVSyncState->synthetic ? State::SyntheticVSync : State::VSync;
} else {
ALOGW_IF(!mVSyncState, "Ignoring VSYNC request while display is disconnected");
// 4.2 没有人申请,则 nextState = SIdle ,下面就会进入等待
nextState = State::Idle;
}
// 第一次执行的时候,mState = Idle ,nextState = VSync
// 后面执行 mState 被设置为 VSync
if (mState != nextState) {
if (mState == State::VSync) {
// 5.1 关闭 Vsync-app
mVSyncSource->setVSyncEnabled(false);
} else if (nextState == State::VSync) {
// 5.2 打开 Vsync-app
mVSyncSource->setVSyncEnabled(true);
}
// 状态同步
mState = nextState;
}
if (event) {
// 6. 有事件则重头开始执行
continue;
}
// Wait for event or client registration/request.
if (mState == State::Idle) {
// 当状态为Idle时,无期限等待
mCondition.wait(lock);
} else {
// Generate a fake VSYNC after a long timeout in case the driver stalls. When the
// display is off, keep feeding clients at 60 Hz.
// 根据当前状态设置超时时间:若为合成VSYNC状态则设置较短超时(16毫秒),其他情况设置为1000毫秒
const std::chrono::nanoseconds timeout =
mState == State::SyntheticVSync ? 16ms : 1000ms;
if (mCondition.wait_for(lock, timeout) == std::cv_status::timeout) {
......// 超时处理,暂时忽略
}
......// 超时处理,暂时忽略
}
}
}
}
-
- 检查是否有 VSync 事件需要处理(如果
VsyncDispatch
产生了VSync
,会放到mPendingEvents
集合中)。
- 检查是否有 VSync 事件需要处理(如果
-
- 遍历
mDisplayEventConnections
集合,其中存放着各个应用的连接。检查哪个应用申请了VSync
,并将其添加到consumers
集合。
- 遍历
-
- 如果
consumers
集合中有元素,则将VSync
事件派发给连接对应的应用**。
- 如果
-
- 确定下一个状态。如果有应用请求了
VSync
,则nextState
为State::VSync
;如果没有,则为State::Idle
。
- 确定下一个状态。如果有应用请求了
-
- 状态切换处理。如果当前状态和下一个状态不相等,会进入这里。通常有两种情况:
- 5.1 当前状态
mState
为State::VSync
,而nextState
为State::Idle
,表示线程要进入等待状态。此时需要关闭VSync-app
信号的派发,执行mVSyncSource->setVSyncEnabled(false)
。 - 5.2 当前状态
mState
为State::Idle
,而nextState
为State::VSync
,表示应用当前处于空闲状态,现在被唤醒,需要申请VSync
。此时需要打开VSync-app
信号的派发,执行mVSyncSource->setVSyncEnabled(true)
。
-
- 事件处理。如果有事件,则从头开始执行循环;如果没有事件,则进入等待逻辑。
- 首次执行时,肯定没有
event
,此时mState
为State::VSync
,所以会进入 7.2,进入超时等待。 - 如果有事件,事件会被分发,然后重新回到循环开始的地方,获取
mPendingEvents
中的下一个元素(如果有的话)。
-
- 等待
- 7.1 无限期等待 (当
mState == State::Idle
时)。 - 7.2 超时等待 (其他情况)。
对应的流程图如下:

要完全理解这个方法,还需要结合它调用的其他逻辑。
参考
<千里马学框架-SurfaceFlinger>
mp.weixin.qq.com/s/gAcBEqjYA...