SurfaceFlinger 与显示系统深入详解
更新: 5/15/2026 字数: 0 字 时长: 0 分钟
1. 显示系统全景
1.1 从代码到屏幕的完整链路
你写的代码 Framework 系统服务 硬件
───────── ────────── ────────── ──────
canvas.bindrawRect()
View.onDraw()
→ RenderThread → Surface → SurfaceFlinger → 显示器
(Skia/HWUI) (BufferQueue) (合成器) (屏幕)
绘制到 Buffer 传递 Buffer 合成所有 Layer 显示一个屏幕上同时有很多东西:状态栏、导航栏、App 窗口、可能还有悬浮窗、Dialog。每个都是独立的 Surface,各自绘制。SurfaceFlinger 的工作就是把这些 Surface 叠在一起,合成最终的一帧画面,送给屏幕显示。
1.2 核心概念一览
Surface = App 拿到的绘制入口(BufferQueue 生产者端的包装)
Buffer = 实际的像素数据(共享内存,跨进程零拷贝)
BufferQueue = App 和 SurfaceFlinger 之间的传送带(2-3 个 Buffer 循环使用)
Layer = SurfaceFlinger 眼中的图层(BufferQueue 消费者端 + 合成信息)
Canvas = 绘制指令接口(提供 bindrawRect/bindrawText 等方法)
SurfaceFlinger = 合成器 + 显示流水线总控
HWC = 硬件合成器(显示控制器,直接驱动屏幕)
Choreographer = App 端的帧调度器(按 VSYNC 节奏调度绘制)2. Surface、Buffer、BufferQueue、Layer 的关系
2.1 四者的对应关系
App 端 中间 SurfaceFlinger 端
────── ────── ──────────────────
Surface ──→ Producer ──→ BufferQueue ──→ Consumer ──→ Layer
[Buffer A]
[Buffer B]
[Buffer C]
Surface 和 BufferQueue 是一对一:一个 Surface 对应一个 BufferQueue
BufferQueue 和 Buffer 是一对多:一个 BufferQueue 里有 2-3 个 Buffer
Layer 和 BufferQueue 是一对一:一个 Layer 持有一个 BufferQueue 的消费者端
Surface 和 Layer 是同一个东西的两端:App 端叫 Surface,SurfaceFlinger 端叫 Layer2.2 Surface:App 的绘制入口
Surface 本身不存储像素数据,它只是 BufferQueue 生产者端的一个句柄:
class Surface {
IGraphicBufferProducer mProducer; // 核心字段,BufferQueue 的生产者接口
// 软件绘制用
public Canvas lockCanvas(Rect dirty) {
// 1. dequeueBuffer() 从 BufferQueue 取一个空闲 Buffer
// 2. 把 Buffer 的内存地址包装成 Canvas
// 3. 返回 Canvas
}
public void unlockCanvasAndPost(Canvas canvas) {
// 1. 解锁 Buffer
// 2. queueBuffer() 提交到 BufferQueue
}
}一个 Activity 至少有一个 Window,一个 Window 有一个 Surface:
Activity
└── PhoneWindow(Window 的实现类)
└── DecorView(根 View)
└── ViewRootImpl
└── Surface
什么时候会有多个 Surface?
Activity 本身的窗口 → Surface 1
弹出一个 Dialog → Surface 2(独立窗口)
SurfaceView → Surface 3(独立于 View 树)2.3 Canvas:绘制指令接口
Canvas 提供所有绘制方法,但它本身不存储像素,需要一个目标:
// 情况 1:画到 Bitmap(内存中的像素数组,和 Surface 无关)
val bitmap = Bitmap.createBitmap(100, 100, ARGB_8888)
val canvas = Canvas(bitmap)
canvas.bindrawRect(...) // 像素写入 bitmap 的内存
// 情况 2:画到 Surface 的 Buffer(屏幕显示用)
val canvas = surface.lockCanvas(dirty)
canvas.bindrawRect(...) // 像素写入 Surface 背后的 GraphicBuffer
surface.unlockCanvasAndPost(canvas)
// 情况 3:画到 DisplayList(硬件加速,只记录命令不执行)
// View.onDraw(canvas) 中的 canvas 实际是 RecordingCanvas
canvas.bindrawRect(...) // 不画像素,只记录命令
// 后续由 RenderThread 用 GPU 执行Surface 和 Canvas 的关系:
Surface 管"画布的生命周期":取出 Buffer、提交 Buffer
Canvas 管"怎么画":bindrawRect、bindrawText、bindrawBitmap
Surface 提供 Buffer → Canvas 往 Buffer 里画 → Surface 提交 Buffer2.4 Buffer(GraphicBuffer):实际的像素数据
Buffer 是一块共享内存:
1080 × 1920 × 4 字节 = 8.3MB 的连续内存
通过 gralloc HAL 分配,跨进程共享:
App 进程 SurfaceFlinger 进程
│ │
│ 同一块物理内存 │
└──→ [像素数据] ←────────────┘
8.3MB
App 写入像素 → SurfaceFlinger 直接读取 → 零拷贝2.5 BufferQueue:生产者-消费者模型
App 进程(生产者) SurfaceFlinger(消费者)
┌──────────────┐ ┌──────────────┐
│ │ dequeueBuffer │ │
│ RenderThread │ ←────────────────────── │ │
│ │ (取一个空 Buffer) │ │
│ │ │ │
│ 绘制内容到 │ │ │
│ Buffer 上 │ │ │
│ │ │ │
│ │ queueBuffer │ │
│ │ ──────────────────────→ │ acquireBuffer│
│ │ (提交填好的 Buffer) │ (取出来合成)│
│ │ │ │
│ │ │ releaseBuffer│
│ │ │ (用完归还) │
└──────────────┘ └──────────────┘Buffer 的状态流转:
FREE App 可以取走绘制
↓ dequeueBuffer(App 取走)
DEQUEUED App 正在绘制
↓ queueBuffer(App 提交)
QUEUED 等待 SurfaceFlinger 取走
↓ acquireBuffer(SurfaceFlinger 取走)
ACQUIRED SurfaceFlinger 正在合成
↓ releaseBuffer(SurfaceFlinger 用完)
FREE 回到起点2.6 Layer:SurfaceFlinger 端的图层
App 看到的是 Surface,SurfaceFlinger 看到的是 Layer。同一个东西的两面:
Layer {
BufferQueue consumer; // 从这里取 Buffer
Rect bounds; // 在屏幕上的位置和大小
int zOrder; // 叠放顺序
float alpha; // 透明度
Matrix transform; // 变换(旋转、缩放)
Region visibleRegion; // 可见区域(被遮挡的部分不需要合成)
int compositionType; // HWC 合成还是 GPU 合成
}屏幕上的实际场景:
三个窗口 = 三套完整链路:
状态栏(SystemUI 进程):
Surface₁ → BufferQueue₁ [B₁ B₂ B₃] → Layer₁ (z=3, 顶部48dp)
App 窗口(你的 App 进程):
Surface₂ → BufferQueue₂ [B₄ B₅ B₆] → Layer₂ (z=2, 中间区域)
导航栏(SystemUI 进程):
Surface₃ → BufferQueue₃ [B₇ B₈ B₉] → Layer₃ (z=1, 底部48dp)2.7 Surface 的创建过程
1. App → WMS:"我要创建一个窗口"
→ WMS 记录窗口信息(位置、大小、Z 轴)
2. WMS → SurfaceFlinger:"创建一个新的 Layer"
→ SurfaceFlinger 创建 Layer + BufferQueue
→ 返回 BufferQueue 的 Producer 端
3. WMS → App:把 Producer 传回
→ App 用它创建 Surface 对象
→ ViewRootImpl 持有这个 Surface
→ App 可以通过 Surface 绘制了3. VSYNC 与 Choreographer
3.1 VSYNC 信号的来源
硬件 VSYNC 由显示控制器(HWC)产生,SurfaceFlinger 接收后通过 DispSync 分发:
显示控制器(HWC)
│ 每 16.6ms 产生一次硬件 VSYNC 中断
▼
HWC HAL
│ 通过回调通知
▼
SurfaceFlinger
│ 交给 DispSync(软件 PLL,平滑抖动)
▼
DispSync
├── 加偏移 1ms → VSYNC-APP → Choreographer(通知 App 开始绘制)
└── 加偏移 5ms → VSYNC-SF → SurfaceFlinger 合成线程(通知开始合成)3.2 为什么要分成 VSYNC-APP 和 VSYNC-SF
❌ 没有偏移(同时开始):
App 和 SurfaceFlinger 同时工作
SF 开始合成时 App 还没画完,没有新 Buffer → 白忙
✅ 有偏移(错开):
VSYNC-APP 先到 → App 先画
VSYNC-SF 后到 → SF 后合成(此时 App 大概率已经画完了)一个 VSYNC 周期内的时间关系:
0ms 1ms 5ms 16.6ms
| | | |
硬件VSYNC VSYNC-APP VSYNC-SF 下一个硬件VSYNC
| |
▼ ▼
Choreographer SurfaceFlinger
通知 App 开始画 通知 SF 开始合成DispSync 还有省电优化:屏幕静止时关闭 VSYNC 接收,有人需要绘制时再重新开启。
3.3 Choreographer 的工作机制
Choreographer 是每个 App 主线程的帧调度器,在 VSYNC-APP 到来时按顺序执行这一帧的所有工作:
public final class Choreographer {
// 五种回调队列,按优先级排列
// [0] CALLBACK_INPUT → 处理输入事件
// [1] CALLBACK_ANIMATION → 属性动画
// [2] CALLBACK_INSETS_ANIMATION → 窗口动画
// [3] CALLBACK_TRAVERSAL → View 的 measure/layout/draw
// [4] CALLBACK_COMMIT → 帧提交后的收尾
private final FrameDisplayEventReceiver mDisplayEventReceiver;
}注册 VSYNC 回调:
当 View 调用 invalidate() 时,最终走到 scheduleTraversals():
// ViewRootImpl
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 1. 插入同步屏障(让异步消息优先)
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 2. 向 Choreographer 注册回调
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL,
mTraversalRunnable, // VSYNC 来了就执行这个
null);
}
}Choreographer 收到注册后,向 SurfaceFlinger 请求下一个 VSYNC-APP:
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
mDisplayEventReceiver.scheduleVsync();
// native 调用,注册到 SurfaceFlinger 的 DispSync
}
}注意:Choreographer 不是一直监听 VSYNC 的。只有当有回调需要执行时才注册,界面静止时不会收到 VSYNC,省电。
VSYNC-APP 到来时:
// FrameDisplayEventReceiver 收到 VSYNC 信号
public void onVsync(long timestampNanos, ...) {
// 发送异步消息到主线程(能穿过同步屏障)
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, ...);
}
// 异步消息被处理时
void doFrame(long frameTimeNanos, int frame) {
// 检查是否掉帧
long skippedFrames = jitter / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
// 按顺序执行五种回调
doCallbacks(CALLBACK_INPUT, frameTimeNanos); // 1. 输入事件
doCallbacks(CALLBACK_ANIMATION, frameTimeNanos); // 2. 动画
doCallbacks(CALLBACK_INSETS_ANIMATION, ...); // 3. 窗口动画
doCallbacks(CALLBACK_TRAVERSAL, frameTimeNanos); // 4. View 绘制
doCallbacks(CALLBACK_COMMIT, frameTimeNanos); // 5. 提交
}为什么按这个顺序:
1. INPUT 先执行 → 触摸事件先处理,可能触发 View 状态变化
2. ANIMATION 其次 → 计算动画值,更新 View 属性
3. TRAVERSAL 最后 → 此时所有状态变化都已发生,统一绘制一次
→ 一帧内先处理所有会导致 UI 变化的事件,最后统一绘制4. 绘制流程:软件绘制 vs 硬件加速
4.1 软件绘制
全部在主线程完成,没有 RenderThread 参与:
// ViewRootImpl.drawSoftware()
Canvas canvas = mSurface.lockCanvas(dirty);
// dequeueBuffer() 从 BufferQueue 取 Buffer
// 把 Buffer 内存包装成 Canvas
mView.bindraw(canvas);
// 从根 View 递归调用每个 View 的 onDraw(canvas)
// CPU 逐像素绘制(Skia 软件渲染)
// 直接写入 Buffer 的像素数据
surface.unlockCanvasAndPost(canvas);
// queueBuffer() 提交到 BufferQueue时间线:
VSYNC-APP
|[measure][layout][lock][CPU绘制所有像素──────────][unlock+post]|
|←──────────── 全部在主线程,全部用 CPU ──────────────────────→|
→ 主线程被绘制阶段长时间占用,期间不能处理触摸、动画4.2 硬件加速
主线程只记录绘制命令,RenderThread 用 GPU 执行:
主线程:
View.bindraw(canvas)
→ canvas 实际是 RecordingCanvas
→ 不真正绘制,只记录命令到 DisplayList
syncFrameState()
→ 把 DisplayList 同步给 RenderThread
→ 主线程空闲了,可以处理其他消息
RenderThread:
→ 遍历 DisplayList
→ 转换为 GPU 指令(OpenGL/Vulkan)
→ GPU 执行渲染,写入 GraphicBuffer
→ eglSwapBuffers() → queueBuffer() 提交到 BufferQueue时间线:
主线程: [measure][layout][记录DisplayList][同步] → 空闲!
RenderThread: [GPU渲染────][提交]
→ 主线程提前释放,可以处理其他消息
→ 主线程和 RenderThread 可以并行4.3 硬件加速的问题
不是所有操作都支持:
canvas.bindrawPicture(picture) // 不支持
paint.setMaskFilter(BlurMaskFilter()) // 部分支持
// 解决:对单个 View 关闭硬件加速
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null)内存占用更大:DisplayList + GPU 纹理缓存 + 离屏缓冲,复杂页面可能额外占用 20-50MB。
离屏渲染:
view.alpha = 0.5f // 半透明:需要先画完整个 View 到临时 Buffer,再整体设置 alpha
// 优化:用 RenderNode 属性代替
view.translationX = 100f // ✅ 不触发离屏
view.scaleX = 1.5f // ✅ 不触发离屏
view.rotation = 45f // ✅ 不触发离屏5. SurfaceFlinger 合成
5.1 两种合成方式
HWC 硬件合成(优先,省电):
显示控制器有多个叠加层(overlay plane)
每个 Layer 分配一个叠加层,硬件直接混合输出
Layer 0 (导航栏) ──→ Overlay Plane 0 ──┐
Layer 1 (App) ──→ Overlay Plane 1 ──┼──→ 显示器(硬件实时混合)
Layer 2 (状态栏) ──→ Overlay Plane 2 ──┘
优点:省电,不占 GPU
限制:叠加层数量有限(通常 4-8 个)GPU 合成(兜底方案):
Layer 太多或需要复杂效果时:
SurfaceFlinger 用 GPU 把多个 Layer 画到一个 Buffer
再把这个 Buffer 交给 HWC 显示实际策略:混合合成:
SurfaceFlinger 每帧询问 HWC 每个 Layer 的处理方式:
状态栏: OVERLAY(简单矩形,HWC 处理)
App 窗口: OVERLAY(简单矩形,HWC 处理)
圆角弹窗: CLIENT(需要 alpha 混合,GPU 处理)
导航栏: OVERLAY5.2 SurfaceFlinger 的合成流程
void SurfaceFlinger::onMessageInvalidate() {
// 1. 从每个 Layer 的 BufferQueue 取最新 Buffer
for (Layer* layer : layers) {
if (layer->hasNewBuffer()) {
layer->acquireBuffer();
}
}
// 2. 计算可见区域、遮挡关系
computeVisibleRegions();
// 3. 询问 HWC 合成策略
hwc.prepare(layers);
// 4. GPU 合成 CLIENT 类型的 Layer
for (Layer* layer : clientLayers) {
renderEngine->bindraw(layer);
}
// 5. 提交给 HWC 输出到显示器
hwc.commit();
// 6. 释放用完的 Buffer(归还给 BufferQueue)
for (Layer* layer : layers) {
layer->releaseBuffer();
}
}6. 双缓冲与三缓冲
6.1 双缓冲的问题
VSYNC: V0 V1 V2 V3
App: [画帧1──────────超时了──]
[画帧2────]
SF: [没有新帧!] [合成帧1──]
Display: [重复旧帧──] [重复旧帧──][显示帧1]
V1 时刻:
Buffer A: App 还在画(DEQUEUED)
Buffer B: 显示器在用
→ 两个 Buffer 都被占着,App 画完帧 1 后没有空闲 Buffer
→ 必须等 V2 才能开始画帧 2 → 连续掉两帧6.2 三缓冲的解决
VSYNC: V0 V1 V2 V3 V4
App: [画帧1──────────超时了──]
[画帧2────] [画帧3────]
↑ 用 Buffer C,不用等!
SF: [没有新帧] [合成帧1──][合成帧2──]
Display: [重复旧帧──][显示帧1──][显示帧2──]
↑ 只掉一帧 ↑ 恢复了
V1 时刻:
Buffer A: App 还在画帧 1
Buffer B: 显示器在用
Buffer C: 空闲!App 可以立即开始画帧 2
→ 只掉一帧就恢复,不会连续掉帧三缓冲的代价:多一帧延迟(约多 16ms),换来更少的连续掉帧。
6.3 稳定状态下三个 Buffer 的分布
Buffer A: DEQUEUED(App 在画下一帧)
Buffer B: ACQUIRED(SurfaceFlinger 在合成)
Buffer C: 显示中(通过 HWC 扫描输出)
→ 三个阶段完全并行,流水线满载7. SurfaceView 与 TextureView
7.1 普通 View
Activity 的所有普通 View 共享同一个 Surface,画在同一个 Buffer 上。
7.2 SurfaceView
SurfaceView 有自己独立的 Surface:
Layer 1: Activity 窗口的 Surface(View 树,SurfaceView 位置透明)
Layer 0: SurfaceView 的 Surface(Z 轴在 Activity 下面)
Activity 的 Surface 上 SurfaceView 的位置是透明的(挖了个洞)
透过这个洞看到下面 SurfaceView 的 Surface
优点:可以在子线程绘制,不影响主线程,适合视频/游戏
缺点:不在 View 树中,不能做 View 动画,不能被其他 View 遮挡7.3 TextureView
TextureView 没有独立的 Surface,用 SurfaceTexture:
内部有一个 BufferQueue,视频/相机内容先画到这里
TextureView.onDraw() 时从中取出最新帧,作为 GPU 纹理
画到 Activity 的 Surface 上
优点:在 View 树中,可以做任何 View 动画
缺点:多一次 GPU 纹理拷贝,必须硬件加速,比 SurfaceView 多一帧延迟8. 一帧的完整旅程
T=0.0ms 硬件 VSYNC 到来 → DispSync 收到
T=1.0ms VSYNC-APP 触发
Choreographer.doFrame()
→ CALLBACK_INPUT: 处理触摸事件
→ CALLBACK_ANIMATION: 计算动画值
→ CALLBACK_TRAVERSAL: performTraversals()
→ measure → layout → draw(记录 DisplayList)
T=6.0ms 主线程完成,DisplayList 同步给 RenderThread
RenderThread 开始 GPU 渲染
T=5.0ms VSYNC-SF 触发
SurfaceFlinger 被唤醒
T=10.0ms RenderThread 完成,queueBuffer() 提交到 BufferQueue
T=10.5ms SurfaceFlinger acquireBuffer() 取出 Buffer
与其他 Layer 一起交给 HWC 合成
T=15.0ms 合成完成,写入 framebuffer
T=16.6ms 下一个硬件 VSYNC
显示器开始扫描新的 framebuffer
用户看到画面更新三级流水线
VSYNC: V0 V1 V2 V3 V4
App: [绘制帧1───] [绘制帧3───]
[绘制帧2───] [绘制帧4───]
SF: [合成帧1──] [合成帧3──]
[合成帧2──] [合成帧4──]
Display: [显示帧1──][显示帧2──][显示帧3──]
在 V2 这个时刻,三件事同时在发生:
App 在绘制帧 3
SurfaceFlinger 在合成帧 2
显示器在显示帧 19. Choreographer 帧监控
// 方案1:postFrameCallback 监控帧间隔
Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
private var lastFrameTimeNanos = 0L
override fun doFrame(frameTimeNanos: Long) {
if (lastFrameTimeNanos != 0L) {
val costMs = (frameTimeNanos - lastFrameTimeNanos) / 1_000_000
if (costMs > 16) {
Log.w("Jank", "帧间隔 ${costMs}ms,掉了 ${costMs / 16 - 1} 帧")
}
}
lastFrameTimeNanos = frameTimeNanos
Choreographer.getInstance().postFrameCallback(this)
}
})
// 方案2:Looper Printer 监控消息处理耗时(BlockCanary 原理)
Looper.getMainLooper().setMessageLogging { msg ->
if (msg.startsWith(">>>>> Dispatching")) {
startTime = SystemClock.elapsedRealtime()
}
if (msg.startsWith("<<<<< Finished")) {
val cost = SystemClock.elapsedRealtime() - startTime
if (cost > 16) {
// dump 主线程堆栈,看在做什么
}
}
}