20、Surface 与 SurfaceControl:Surface 的创建与销毁、SurfaceControl 的作用、SurfaceFlinger 的关系
好,咱们今天来聊聊 Surface 和 SurfaceControl。这两个东西,说白了就是 Android 图形系统里最核心的「画布」和「画布管理员」。我刚开始啃这部分源码的时候,也绕了不少弯路。你想想看,一个 App 要往屏幕上画东西,总得有个地方画吧?Surface 就是那个地方。但谁去管理这块画布什么时候能用、什么时候该销毁?那就是 SurfaceControl 的活了。
Surface 是什么?
Surface 本质上是一块共享内存,它对应着 SurfaceFlinger 里的一个 BufferQueue 的生产端。App 端通过 Canvas 或者 OpenGL ES 往这块内存里画图,画完之后,Surface 就把这块 buffer 交给 SurfaceFlinger 去合成显示。
我个人的习惯是,把 Surface 理解成一个「生产者的接口」。它不直接管理内存的分配和回收,它只负责往 BufferQueue 里塞数据。
核心要点:Surface 是 App 进程与 SurfaceFlinger 进程之间传递图形数据的桥梁。它本身不直接操作硬件,只负责生产图形数据。
Surface 的创建流程
Surface 的创建,通常发生在 WindowManagerService 为某个 Window 分配 Surface 的时候。流程大致是这样的:
- App 请求创建 Window:比如 Activity 启动时,通过 ViewRootImpl 向 WMS 请求添加一个 Window。
- WMS 创建 SurfaceControl:WMS 内部会调用
SurfaceControl.create(),这时候会通过 Binder 调用到 SurfaceFlinger 去创建一个 Layer。 - SurfaceFlinger 分配 BufferQueue:SurfaceFlinger 为这个 Layer 创建一个 BufferQueue,并把生产端(IGraphicBufferProducer)通过 Binder 传回给 WMS。
- WMS 创建 Surface:WMS 拿到 IGraphicBufferProducer 后,用它来构造一个 Surface 对象,然后传给 App 进程。
嗯,这里要注意:Surface 的创建是跨进程的。App 拿到的 Surface 其实是一个代理对象,真正的 BufferQueue 在 SurfaceFlinger 那边。
// 简化的创建流程伪代码
// WMS 端
SurfaceControl surfaceControl = new SurfaceControl.Builder()
.setName("MyWindow")
.setBufferSize(width, height)
.build();
// 拿到 Surface
Surface surface = surfaceControl.getSurface();
// 然后传给 App 的 ViewRootImpl
Surface 的销毁
Surface 的销毁比创建要稍微复杂一点。为什么?因为涉及到引用计数和跨进程同步。
我记得有一次在项目中遇到一个 bug:App 退出了,但 Surface 对应的 Layer 在 SurfaceFlinger 里迟迟没有销毁,导致显存泄漏。后来排查发现,是 App 进程的 Surface 对象没有被正确释放。
Surface 的销毁流程:
- App 端释放:App 调用
Surface.release(),这会断开与 BufferQueue 的连接。 - WMS 端销毁 SurfaceControl:WMS 调用
SurfaceControl.release(),这会通知 SurfaceFlinger 销毁对应的 Layer。 - SurfaceFlinger 回收资源:SurfaceFlinger 把 Layer 从合成列表中移除,并释放 BufferQueue 和相关的 GraphicBuffer。
注意:Surface 和 SurfaceControl 的生命周期是独立的。Surface 可以提前释放,但 SurfaceControl 必须由 WMS 来管理。我曾经见过开发者直接 new Surface() 然后忘记 release,结果内存暴涨。
SurfaceControl 的作用
SurfaceControl 是 WMS 用来控制 Surface 的「遥控器」。它不直接持有图形数据,但它可以控制 Surface 的各种属性:
| 属性 | 说明 | 我踩过的坑 |
|---|---|---|
| 位置 (Position) | 控制 Surface 在屏幕上的坐标 | 曾经忘记在屏幕旋转时更新位置,导致画面偏移 |
| 大小 (Size) | 控制 Surface 的宽高 | 尺寸设置不对,SurfaceFlinger 会直接裁剪 |
| 透明度 (Alpha) | 控制整个 Surface 的透明度 | 动画时频繁修改,性能开销不小 |
| Z 轴顺序 (Layer) | 控制 Surface 的叠放层级 | 数值越大越靠上,这个容易搞反 |
| 可见性 (Visibility) | 显示或隐藏 Surface | 隐藏时记得释放 buffer,否则浪费显存 |
说白了,SurfaceControl 就是 WMS 用来跟 SurfaceFlinger 沟通的「命令通道」。WMS 通过它告诉 SurfaceFlinger:「这个 Layer 该挪到哪儿了」、「这个 Layer 该隐藏了」。
与 SurfaceFlinger 的关系
这三者的关系,我用一张图来展示:
从这张图可以看得很清楚:App 只管往 Surface 里画图,画完就丢给 SurfaceFlinger。WMS 通过 SurfaceControl 告诉 SurfaceFlinger 怎么处理这些图。SurfaceFlinger 是最终的执行者。
避坑指南
我这些年跟 Surface 打交道,踩过不少坑,分享几个典型的:
- Surface 泄漏:我曾经在自定义 SurfaceView 时,忘记在 onDetachedFromWindow 里释放 Surface,结果 Activity 销毁了 Surface 还在,导致 SurfaceFlinger 那边的 Layer 一直挂着。后来加了个 try-finally 保证 release 调用。
- 跨进程 Binder 调用超时:SurfaceControl 的很多操作(如 setPosition、setAlpha)都是通过 Binder 调用到 SurfaceFlinger 的。如果调用太频繁,Binder 线程池可能会满,导致 ANR。我建议批量操作时使用 Transaction 合并。
- Buffer 饥饿:如果 App 生产 buffer 的速度跟不上 SurfaceFlinger 消费的速度,或者反过来,就会出现掉帧。我曾经在某个项目里用 triple buffering 解决了这个问题。
个人建议:如果你需要直接操作 Surface,尽量使用 SurfaceControl.Transaction 来批量提交属性变更。这样能减少 Binder 调用次数,性能会好很多。
好了,关于 Surface 和 SurfaceControl 的核心内容就这些。你只要记住:Surface 是画布,SurfaceControl 是遥控器,SurfaceFlinger 是那个真正干活的人。这三者的关系搞清楚了,Android 图形系统的一半你就拿下了。
公众号:蓝海资料掘金营,微信deep3321