3、SurfaceFlinger启动流程:进程创建、主循环与消息处理、初始化关键步骤、VSync信号机制
各位同学,今天我们来聊聊SurfaceFlinger的启动流程。说实话,这块内容我当年啃源码的时候也花了不少时间。你想想看,一个负责整个屏幕合成的系统服务,它的启动过程必然藏着很多设计上的考量。我个人习惯把启动流程拆成四个部分来看:进程怎么来的、主循环怎么转的、初始化做了哪些关键事、VSync信号又是怎么产生的。
3.1 SurfaceFlinger进程创建
SurfaceFlinger不是普通App,它是一个系统服务。在Android系统中,它由init进程启动。具体来说,是在init.rc文件中定义的。
关键点:SurfaceFlinger是一个独立的native进程,不是binder线程池里的一个服务。
我们来看一下init.rc中的相关配置:
service surfaceflinger /system/bin/surfaceflinger
class core
user system
group graphics drmrpc
onrestart restart zygote
writepid /dev/cpuset/system-background/tasks
shutdown critical
嗯,这里要注意几个细节。第一,它属于core类,意味着系统核心服务启动时它就要起来。第二,它挂了会重启zygote——为什么?因为zygote依赖SurfaceFlinger做图形合成,SurfaceFlinger挂了,所有App界面都会卡死,不如一起重启。我在项目中遇到过几次SurfaceFlinger异常重启导致整机重启的情况,排查起来确实头疼。
进程启动后,main函数入口在main_surfaceflinger.cpp中:
int main(int, char**) {
// 启动binder线程池
ProcessState::self()->setThreadPoolMaxThreadCount(4);
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool();
// 创建SurfaceFlinger实例
sp<SurfaceFlinger> flinger = new SurfaceFlinger();
// 初始化
flinger->init();
// 进入主循环
flinger->run();
return 0;
}
这段代码看起来简单,但每一步都藏着玄机。我建议你记住这个流程:创建实例 → 初始化 → 进入主循环。后面我们会逐一拆解。
3.2 主循环与消息处理
SurfaceFlinger的主循环不是简单的while(true),它基于Looper机制。说白了,它和Android App的Handler机制是同一套东西,只不过跑在native层。
我们看看SurfaceFlinger::run()的核心逻辑:
void SurfaceFlinger::run() {
do {
// 等待消息
waitForEvent();
// 处理消息
if (CC_LIKELY(mStateDisplay->isPoweredOn())) {
onMessageReceived(message);
}
} while (true);
}
这里有个细节我当年踩过坑。我曾经以为waitForEvent()就是简单的阻塞等待,后来发现它内部其实处理了VSync信号、Binder调用、定时器等多种事件源。它用的是Looper::pollOnce(),可以同时监听多个fd。
避坑指南:我曾经在调试一个显示延迟问题时,发现主循环被某个耗时操作卡住了。后来我养成了一个习惯——在关键路径上加ALOGD打印时间戳,这样能快速定位是哪个环节拖慢了帧率。
消息处理的核心是onMessageReceived(),它根据消息类型分发:
void SurfaceFlinger::onMessageReceived(int32_t what) {
switch (what) {
case MessageQueue::INVALIDATE:
handleMessageInvalidate();
break;
case MessageQueue::REFRESH:
handleMessageRefresh();
break;
}
}
INVALIDATE消息负责处理Layer状态的变更,比如App提交了新帧。REFRESH消息则负责真正的合成和显示。这两个消息的配合,构成了SurfaceFlinger的核心工作流。
3.3 初始化关键步骤
初始化函数SurfaceFlinger::init()做的事情非常多,我挑几个最重要的来说。
| 初始化步骤 | 作用 | 注意事项 |
|---|---|---|
| 创建HWComposer | 与硬件显示控制器通信 | 不同芯片的HAL实现差异很大 |
| 创建EventThread | 产生VSync信号 | 需要与DisplayHardware配合 |
| 初始化DisplayDevice | 管理物理显示屏 | 支持多屏时要注意主屏选择 |
| 启动BootAnimation | 显示开机动画 | 如果SurfaceFlinger启动慢,开机动画会卡 |
我个人觉得最关键的步骤是HWComposer的初始化。它直接决定了你的显示芯片能不能正常工作。我记得有一次移植到一款新平台,HWComposer初始化总是失败,最后发现是HAL层的接口版本不匹配。嗯,这种问题排查起来真的很费时间。
我们来看一下HWComposer初始化的核心代码:
void SurfaceFlinger::init() {
// 1. 初始化HWComposer
mHwc = new HWComposer(getFactory());
if (mHwc->initCheck() != OK) {
// 这里会直接导致SurfaceFlinger启动失败
ALOGE("HWComposer initialization failed!");
return;
}
// 2. 创建EventThread
mEventThread = new EventThread(mHwc);
mEventThread->run("EventThread", PRIORITY_URGENT_DISPLAY);
// 3. 初始化DisplayDevice
for (size_t i = 0; i < mHwc->getNumDisplays(); i++) {
sp<DisplayDevice> display = new DisplayDevice(this, i);
mDisplays.add(i, display);
}
// 4. 启动BootAnimation
startBootAnimation();
}
警告:初始化顺序不能乱。比如EventThread必须在DisplayDevice之前创建,因为DisplayDevice需要注册VSync监听。我曾经见过有人把顺序搞反了,结果VSync信号一直收不到,屏幕刷新率直接掉到0。
3.4 VSync信号机制
VSync是SurfaceFlinger的心脏。没有VSync,整个显示系统就会乱套。你想想看,如果没有VSync,App可能在屏幕正在刷新的时候提交新帧,导致画面撕裂。
VSync信号的产生流程是这样的:
- 硬件产生垂直同步中断(VBlank interrupt)
- HWComposer的HAL层捕获这个中断
- EventThread从HWComposer读取VSync时间戳
- EventThread通知所有注册的监听者
我们来看看EventThread的核心逻辑:
bool EventThread::threadLoop() {
// 等待VSync事件
nsecs_t timestamp = mHwc->getVSyncTimestamp();
if (timestamp > 0) {
// 分发VSync给所有监听者
for (auto& listener : mListeners) {
listener->onVSyncReceived(timestamp);
}
}
return true;
}
这里有个重要的设计:VSync信号是分发的,不是轮询的。每个监听者(比如App的Choreographer、SurfaceFlinger自己)都会注册到EventThread上。当VSync到来时,EventThread会依次通知它们。
个人经验:我曾经在调试一个帧率不稳的问题时,发现VSync信号的时间戳偶尔会跳变。后来定位到是HWComposer的HAL层实现有bug,在屏幕休眠唤醒后没有正确重置VSync计数器。这个坑让我学会了——永远不要完全信任硬件驱动,一定要在软件层做校验。
VSync信号在SurfaceFlinger内部有两个主要消费者:
- INVALIDATE消息:收到VSync后,SurfaceFlinger会检查是否有新的Layer需要处理。如果有,就发送INVALIDATE消息给自己。
- REFRESH消息:在INVALIDATE处理完成后,SurfaceFlinger会发送REFRESH消息,触发真正的合成和显示。
这个机制保证了每一帧的处理都在VSync的节奏下进行。说白了,就是让所有操作都同步到显示器的刷新周期上。
最后,我给大家留一个思考题:如果VSync信号的频率和显示器的刷新率不匹配,会发生什么?嗯,这个问题我在下一节课会详细讲,但你可以先想想看。