3. Android 显示框架:WindowManager、SurfaceFlinger、Hardware Composer 的角色关系
Android 的显示系统说白了就是三个大管家:WindowManager 管窗口,SurfaceFlinger 管合成,Hardware Composer 管硬件输出。这三者配合得好,屏幕刷起来就丝滑;配合不好,你就能感受到掉帧的酸爽。我自己在优化启动速度时,就吃过它们之间 buffer 延迟的亏——今天我们来把每个角色的职责和协作关系彻底掰开揉碎。
核心组件概览
先把三者的定位用一张表说清楚,方便后面深入聊。
| 组件 | 英文全称 | 核心职责 | 进程/服务 |
|---|---|---|---|
| WindowManager(WMS) | Window Manager Service | 窗口的添加、移除、层级排序、输入事件分发 | system_server |
| SurfaceFlinger(SF) | SurfaceFlinger | 将多个 Surface 合成到 BufferQueue,再提交给 HWC | surfaceflinger 进程 |
| Hardware Composer(HWC) | Hardware Composer HAL | 硬件加速合成、Overlay 合成、直接输出显示 | HAL 层(hwcomposer XYZ) |
adb shell dumpsys window windows 查看当前所有窗口的信息,这条命令会直接吐出 WMS 内部的窗口树。
WindowManager:窗口的门童与调度员
WMS 位于 system_server 进程,它管理着所有应用和系统窗口的生命周期。包括但不限于 Activity 窗口、Dialog、Toast、输入法窗口,以及我们常说的悬浮窗。嗯,这里要注意——窗口的 z-order(层级顺序)由 WMS 统一决定,应用想把自己提到最上面?没门,必须经过 WMS 同意。
WMS 还会把窗口的尺寸、位置、Transformation 信息打包进 WindowState,然后通过 SurfaceControl 通知 SurfaceFlinger 创建或更新对应的 Surface。说白了,WMS 就是个协调者——它不直接画任何东西,但每个像素的位置它说了算。
我个人习惯在分析窗口布局问题时,先看 dumpsys window policy 的输出,那里能直接看到焦点窗口和屏幕旋转状态。
SurfaceFlinger:合成引擎,缓冲区的
SurfaceFlinger 运行在独立的 surfaceflinger 进程。它接收来自 WMS 和其他进程(比如 App 的 ViewRootImpl)的 Surface,然后对它们进行图形合成。合成的结果放入一个 BufferQueue 中,最终交给 HWC 去显示。
SF 的工作模式是典型的 生产者-消费者:
- 生产者:App 的 UI 线程通过
Canvas/Skia/Vulkan渲染到 Surface 上。 - 消费者:SurfaceFlinger 在 Vsync 信号到来时,消费 BufferQueue 里的已就绪 buffer,进行合成。
- 最终消费者:HWC 拿到合成结果后,输出给屏幕。
这里有个坑,我曾经遇到过:App 渲染太慢,导致 SurfaceFlinger 在 Vsync 时刻拿不到 buffer,只能重复上一次的 buffer,这就产生了 jank(卡顿)。所以监控 adb shell dumpsys SurfaceFlinger --latency 是排查掉帧的必备手段。
// 示例:查看 SurfaceFlinger 的帧延迟统计
adb shell dumpsys SurfaceFlinger --latency // 后面可以接具体图层名
// 输出字段解读:
// 第一列:Vsync 时间戳(纳秒)
// 第二列:App 渲染完成并提交 buffer 的时间
// 第三列:SF 实际 Present 时间
// 差值越大,说明掉帧越严重
SurfaceFlinger 的 CompositionStrategy 配置不当,导致 GPU 合成占比过高,功耗直接拉升。建议在 /vendor/etc/surfaceflinger.rc 中检查 debug.sf.hwc_layer_count 参数,它决定了 HWC 能处理的最大图层数。超出部分会被 SF 强制用 GPU 合成。
Hardware Composer:硬件加速的最后一棒
HWC 是 Android 显示体系中离硬件最近的一层。它是一个 HAL(硬件抽象层)模块,每个芯片厂商(高通、MTK、三星等)都会有各自的实现。HWC 做的事情很简单,但也最难:把 SurfaceFlinger 传下来的合成指令,通过硬件加速器(如 Display Controller、Overlay Engine)直接输出到屏幕。
HWC 决定哪些图层可以用硬件 Overlay 合成(零 CPU/GPU 开销),哪些必须 fallback 到 GPU 合成。常见的 Overlay 数量限制是 3~5 层(取决于硬件)。如果你的界面有 6 个图层,那么至少有一层会走 GPU,带来额外功耗。
我记得有一次排查某个游戏在横屏时帧率骤降,最后发现是因为游戏多了一个状态栏浮层,导致图层数超过 HWC 限制,被迫走 GPU 合成。调整图层层级后,问题解决了。
三者协作流程(附 SVG 关系图)
下面这张图展示了从 App 窗口创建到最终显示到屏幕的整体数据流。我把它画成三个阶段:窗口管理 → 合成 → 硬件输出。
协作流程详解
- App 向 WMS 请求添加窗口:WMS 分配
WindowState,并创建一个SurfaceControl通知 SF 分配一块BufferQueue。 - App 渲染:App 将 UI 绘制到自己的 Surface 上,填充 BufferQueue 中的 buffer。
- SF 合成:在 Vsync 信号到来时,SF 取出所有待消费 buffer,通过 GPU 或硬件 Overlay 进行合成。合成结果写入一个新的 buffer。
- SF 向 HWC 提交:SF 调用
HWC::set()传入图层列表(包含每个图层的 buffer 句柄和变换矩阵)。HWC 决定哪些图层用硬件 Overlay,哪些用 GPU fallback。 - HWC 输出:HWC 将最终帧送到 Display Controller,屏幕刷新显示。
- 图层数量不要超过 HWC 的硬件 Overlay 限制(通常 3~5 层),否则会触发 GPU 合成,增加功耗和延迟。
- SurfaceFlinger 的 BufferQueue 长度如果设置过小(默认一般是 2~3),在 App 生产过快时可能导致 buffer 溢出或等待。
- 检查 VSync 偏移(
sf_pts)是否合理——我见过一些设备因为偏移设置不当导致持续丢帧。
总结要点
别小看这三个组件的分工。WMS 是决策层,SF 是执行层,HWC 是加速层。你想想看,如果没有 HWC,所有合成都走 GPU,手机发烫不说,高刷屏根本跑不动。个人经验:在做系统性能调优时,先看 dumpsys SurfaceFlinger 里的 hwc_layer_count 和 gpu_composition_count,就能快速判断瓶颈在哪个环节。
最后说一句:如果你在调试过程中发现界面卡顿,先别急着怀疑 App 渲染——去查查 WMS 是否频繁调整窗口大小(比如输入法弹出导致 relayout),或者 HWC 是否 fallback 了过多 Overlay。我踩过这个坑,希望你不用再踩一次。