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 等。
⚠ 注意:不要混淆“合成”和“输出”。合成是把多个图层变成一张图,输出是把这张图送显。很多开发者觉得掉帧就是合成太慢,但有时候其实是 HWC 往显示驱动送数据延迟了。我曾在一个项目里遇到显示驱动申请 DMA 缓冲区过慢,导致 SurfaceFlinger 卡在 queueBuffer 上,现象就是每隔几秒跳帧。

3.3 核心职责:帧率控制

帧率控制就是让整个流水线以稳定的速率运行。Android 里靠 Choreographer(App 侧)和 SurfaceFlinger(系统侧)的 VSYNC 机制配合。

  1. App 端:通过 Choreographer 监听 VSYNC,收到信号后开始绘制。绘制完成后通过 eglSwapBuffers 或 queueBuffer 把 Buffer 交给 BufferQueue。
  2. SurfaceFlinger 端:同样监听 VSYNC,在 VSYNC 到来时检查所有 BufferQueue,挑选最新的 Buffer 进行合成。
  3. 帧率同步:如果 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 停滞。

💡 避坑指南:我曾经遇到一个应用使用 SurfaceView 播放视频时,频繁触发 SurfaceTexture.onFrameAvailable,但 SurfaceFlinger 因为图层数太多(同时有多个 SurfaceView)来不及合成,导致 Buffer 堆积。最终解决方案是减少 SurfaceView 数量,改用 TextureView 并合并图层。
SurfaceFlinger 核心流程与交互 App 进程 绘制 → queueBuffer BufferQueue 共享内存 / Binder SurfaceFlinger 合成 / 释放 HWC 硬件叠加 / 派发 Display 刷新 / 显示 VSYNC 定时脉冲(16.6ms) Choreographer App 侧 VSYNC 回调 流程图:App 通过 BufferQueue 与 SurfaceFlinger 交换 Buffer → SurfaceFlinger 合成后交由 HWC 或 GPU 输出到 Display VSYNC 协调两端帧率,Choreographer 通知 App 开始绘制,SurfaceFlinger 触发合成

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 画好的图推出去,而我们要做的,就是别让它卡住。