SurfaceFlinger 到底在忙什么?
说白了,SurfaceFlinger 就是 Android 显示系统的“大管家”。它接收来自各个 App 的绘图结果,然后把这些图层合成一帧,再交给屏幕去刷新。如果你用过 Photoshop 里的图层面板,道理差不多——App 画各自的图层,SurfaceFlinger 负责把图层叠起来并输出最终画面。
我经常在调试卡顿时被人问:“SurfaceFlinger 不就是个合成的吗?能有什么复杂的?”嗯,实际上它的职责远不止“拼图”。它要管合成、管输出、管帧率,还要跟 App 端博弈。今天我们就把它一层层剥开。
3.1 核心职责:合成
合成是 SurfaceFlinger 最基础的工作。它从 BufferQueue 里取出每个 Layer 的 Buffer,然后决定用 GPU 还是硬件合成器(HWC)来混合。
- Buffer 管理:每个 App 的 Surface 背后都有一个 BufferQueue。App 生产 Buffer,SurfaceFlinger 消费 Buffer。当 Buffer 被消费完,SurfaceFlinger 会通过 releaseFence 把 Buffer 还给 App 继续画。
- 合成策略:HWC 优先,GPU 兜底。HWC 能利用硬件叠加器直接在显示控制器里合成多个图层,功耗低、性能好。如果 HWC 不支持(比如图层带透明度、旋转等),则回退到 GPU 通过 OpenGL ES 合成到一块 Framebuffer 上。
- 我遇到过的坑:曾经在低端机上遇到 HWC 合成失败导致 GPU 负载飙升。后来发现是有个悬浮窗带了半透明效果,HWC 不支持半透明叠加,结果 SurfaceFlinger 只能走 GPU 合成。解决办法是把悬浮窗改成全屏不透明,或者强制使用 GPU 合成但降低图层数。
你想想看,合成这步如果出问题,最直接的现象就是“掉帧”——因为 GPU 或 HWC 来不及处理那么多层。所以优化合成,核心就是减少图层数、降低每个图层的复杂度。
3.2 核心职责:输出
合成完之后,SurfaceFlinger 要把结果推到显示器上。这个“推”的动作受 VSYNC 控制。
- VSYNC 脉冲:硬件每隔 16.6ms(60Hz)或更短时间发出一个垂直同步信号。SurfaceFlinger 在每个 VSYNC 到来时触发合成和提交。
- 双缓冲/三缓冲:App 和 SurfaceFlinger 之间通过 BufferQueue 交换 Buffer。SurfaceFlinger 持有当前帧的 Buffer,App 准备下一帧。如果 App 绘制太慢,SurfaceFlinger 可能会重复显示上一帧,这就是“掉帧”。
- 输出到 Display:最终通过 HWC 或 Framebuffer 发送到显示驱动。Display 端也有自己的缓冲区,叫 SFB(SurfaceFlinger Buffer)或 Panel Self Refresh 等。
3.3 核心职责:帧率控制
帧率控制就是让整个流水线以稳定的速率运行。Android 里靠 Choreographer(App 侧)和 SurfaceFlinger(系统侧)的 VSYNC 机制配合。
- App 端:通过 Choreographer 监听 VSYNC,收到信号后开始绘制。绘制完成后通过 eglSwapBuffers 或 queueBuffer 把 Buffer 交给 BufferQueue。
- SurfaceFlinger 端:同样监听 VSYNC,在 VSYNC 到来时检查所有 BufferQueue,挑选最新的 Buffer 进行合成。
- 帧率同步:如果 App 绘制时间超过 16.6ms,就会错过当前 VSYNC,只能等下一个。这就是“掉一帧”。如果持续掉帧,SurfaceFlinger 会启动“掉帧补偿”,比如跳过合成直接显示上一帧。
我自己的习惯是,用 dumpsys gfxinfo 看看 App 的FrameStats,再用 systrace 抓 SurfaceFlinger 的合成和提交时间。只要看到 SF 的合成时间超过 16ms,基本就能断定是合成瓶颈。
SurfaceFlinger 合成耗时 < 4ms(理想),< 8ms(可接受),>16ms(严重掉帧)。
HWC 合成优于 GPU 合成,GPU 合成应作为最后手段。
3.4 与 App 侧的交互机制
整个交互流程可以画成一条流水线:
- App 绘制:使用 Canvas 或 OpenGL 渲染到 Surface 上。Surface 内部是一个 BufferQueue 的生产者端。
- Buffer 提交:App 调用 `sLockCanvas()` 或 `dequeueBuffer` 申请 Buffer,绘制完后调用 `unlockCanvasAndPost` 或 `queueBuffer` 把 Buffer 提交到 BufferQueue。
- SurfaceFlinger 消费:在 VSYNC 触发时,SurfaceFlinger 调用 `acquireBuffer` 取出 Buffer,然后做合成。
- 释放 Buffer:合成完毕后,SurfaceFlinger 通过 `releaseBuffer` 把 Buffer 还给 BufferQueue,并通知 App 可以继续画下一帧。
注意一个细节:Buffer 是在 SurfaceFlinger 和 App 之间跨进程传递的(通过 Binder 和共享内存)。这导致了一个经典问题 —— Buffer 饥饿。如果 SurfaceFlinger 消费太慢,App 申请不到 Buffer,就会卡在 dequeueBuffer 上,导致 UI 停滞。
3.5 总结一下要点
| 职责 | 核心动作 | 常见问题 |
|---|---|---|
| 合成 | 从 BufferQueue 获取 Buffer,HWC 或 GPU 混合图层 | 图层过多导致 GPU 合成耗时增加 |
| 输出 | 将合成后的 Buffer 提交到显示驱动 | 显示驱动 DMA 延迟、VSYNC 偏移 |
| 帧率控制 | 配合 VSYNC 和 Choreographer 同步 App 与 SF | 掉帧、Buffer 饥饿、合成超时 |
systrace 看 SF 的 onMessageReceive 和 handleMessageTransaction。如果看到大量“Fence wait”或者“acquireBuffer timeout”就说明 App 端或者 BufferQueue 在拖后腿。如果看到“drawFrame”耗时过长,那就是合成或 HWC 的问题。
嗯,SurfaceFlinger 的核心职责就是这么回事。很多优化其实都围绕着减少它的负载:减少图层数、降低缓冲区规格、避免走 GPU 合成、保障 VSYNC 稳定。记住一句话:SurfaceFlinger 能做的,就是尽可能快地把 App 画好的图推出去,而我们要做的,就是别让它卡住。