12、Surface与SurfaceTexture:Surface创建、SurfaceTexture工作原理、Consumer/Producer模型、Buffer流转
好,我们进入第十二章。说实话,Surface 和 SurfaceTexture 这对兄弟,是 Android 多媒体框架里最容易被搞混的概念。我刚入行那会儿,也在这上面栽过跟头——明明看着都是“表面”,怎么一个能画,一个不能直接画?
今天咱们就把它们彻底掰扯清楚。
12.1 Surface 是什么?
Surface 本质上是一块“画布”。它代表了一个可绘制的缓冲区队列的入口。你拿着 Canvas 往上面画东西,或者用 OpenGL ES 往上面渲染,最终这些内容都会被送到 BufferQueue 里,交给消费者处理。
我习惯把 Surface 理解成“生产者的门面”。生产者(比如你的 App 的 UI 线程、MediaCodec 的编码器)只需要知道 Surface 这个对象,往里丢数据就行,至于数据去了哪里、怎么被消费,它不关心。
关键点: Surface 本身不存储像素数据。它只是一个句柄,背后连接着一个 BufferQueue 的生产者端。
12.1.1 Surface 的创建
Surface 的创建方式有好几种,最常见的是通过 SurfaceView 或 TextureView 来获取。但如果你需要自己创建一个“离屏”的 Surface,比如用于后台渲染或录制,可以用 Surface(SurfaceTexture) 这个构造方法。
// 创建一个离屏 Surface
SurfaceTexture surfaceTexture = new SurfaceTexture(/* 纹理ID */);
Surface surface = new Surface(surfaceTexture);
// 使用完后记得释放
surface.release();
surfaceTexture.release();
嗯,这里要注意:Surface 的构造方法接收的是一个 SurfaceTexture 对象。也就是说,Surface 和 SurfaceTexture 天生就是一对搭档。
12.2 SurfaceTexture 的工作原理
SurfaceTexture 是消费者端的代表。它接收来自 BufferQueue 的图像数据,并将其绑定到一个 OpenGL ES 纹理上。说白了,SurfaceTexture 就是一个“桥梁”——把 BufferQueue 里的数据,变成 GPU 能直接用的纹理。
我曾经在做一个相机预览的优化项目时,发现预览画面总是有延迟。排查了半天,最后发现是 SurfaceTexture 的 updateTexImage() 调用时机不对。这个函数必须在 GL 线程里调用,而且每次调用都会从 BufferQueue 里取出一帧最新的数据。
个人经验: SurfaceTexture 的 onFrameAvailableListener 回调是在任意线程触发的,但 updateTexImage() 必须在 GL 线程执行。我建议用 Handler 把回调切到 GL 线程,避免线程安全问题。
12.3 Consumer/Producer 模型
这个模型是 Android 图形系统的核心。说白了,就是“谁生产,谁消费”的问题。
- Producer(生产者): 往 BufferQueue 里填充数据。比如 Camera HAL、MediaCodec 编码器、Canvas 绘制。
- Consumer(消费者): 从 BufferQueue 里取出数据并处理。比如 SurfaceFlinger、SurfaceTexture、ImageReader。
BufferQueue 本身是一个循环队列,通常有 3 到 4 个缓冲区。生产者请求一个空闲缓冲区,填满数据后入队;消费者从队列头部取出一个已填满的缓冲区,处理完后归还给队列。
你想想看,如果没有这个模型,每个模块都直接操作显存,那系统早就乱套了。BufferQueue 起到了解耦和流控的作用。
避坑指南: 我曾经遇到过一个 bug,生产者生产速度远快于消费者消费速度,导致 BufferQueue 满了之后生产者被阻塞。结果 UI 线程卡死,因为 Surface 的 dequeueBuffer 操作是同步的。解决方案是增加缓冲区数量,或者降低生产速率。
12.4 Buffer 流转过程
咱们用一张图来展示 Buffer 的完整生命周期:
整个流程其实就四个步骤:
- dequeueBuffer: 生产者从 BufferQueue 里申请一个空闲的 Buffer。如果没有空闲的,生产者会被阻塞。
- queueBuffer: 生产者把数据填进 Buffer 后,将其入队,并通知消费者。
- acquireBuffer: 消费者从队列头部取出一个已填满的 Buffer 进行处理。
- releaseBuffer: 消费者处理完后,把 Buffer 归还给队列,供生产者再次使用。
这个模型的好处是:生产者和消费者完全解耦,各自运行在自己的线程里。只要 BufferQueue 的深度设置合理,就不会出现互相等待的死锁问题。
小技巧: 如果你在做相机开发,可以通过 CameraCharacteristics 查询设备支持的 Buffer 数量。不同设备的 BufferQueue 深度可能不一样,有的只有 3 个,有的有 5 个。了解这个参数有助于你优化预览延迟。
12.5 Surface 与 SurfaceTexture 的关系
咱们用一张表格来对比一下:
| 特性 | Surface | SurfaceTexture |
|---|---|---|
| 角色 | 生产者端 | 消费者端 |
| 主要用途 | 绘制、渲染、编码输入 | 接收图像数据,绑定到 GL 纹理 |
| 是否可显示 | 是(通过 SurfaceFlinger) | 否(需要手动渲染到 GLSurfaceView 等) |
| 线程要求 | 任意线程 | updateTexImage() 必须在 GL 线程 |
| 典型场景 | Camera 预览、MediaCodec 编码 | 相机预览帧处理、视频帧滤镜 |
说白了,Surface 是“往外吐”的,SurfaceTexture 是“往里收”的。它们通过 BufferQueue 连接在一起,构成了 Android 图形系统里最基础的数据通道。
总结一下: 如果你只是想显示画面,用 SurfaceView 或 TextureView 就够了。但如果你需要对每一帧图像做处理(比如加滤镜、做分析),那就必须用 SurfaceTexture。我个人的习惯是:能用 SurfaceTexture 的地方尽量用,因为它给了你更多的控制权。
好了,这一章的内容就到这里。Surface 和 SurfaceTexture 的关系,说白了就是生产者和消费者的关系。理解了 BufferQueue 的流转机制,你就能明白 Android 图形系统为什么能如此高效地处理图像数据。