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 的完整生命周期:

Buffer 流转生命周期 Producer Camera / Canvas / Codec dequeueBuffer BufferQueue Buffer 0 (空闲) Buffer 1 (已填满) Buffer 2 (处理中) queueBuffer Consumer SurfaceFlinger / GL acquireBuffer releaseBuffer 1. Producer 请求空闲 Buffer → 2. 填充数据后入队 3. Consumer 取出已填满 Buffer → 4. 处理完后归还

整个流程其实就四个步骤:

  1. dequeueBuffer: 生产者从 BufferQueue 里申请一个空闲的 Buffer。如果没有空闲的,生产者会被阻塞。
  2. queueBuffer: 生产者把数据填进 Buffer 后,将其入队,并通知消费者。
  3. acquireBuffer: 消费者从队列头部取出一个已填满的 Buffer 进行处理。
  4. 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 图形系统为什么能如此高效地处理图像数据。


公众号:蓝海资料掘金营,微信deep3321