Surface 是什么、以及它在 TextureView 和 SurfaceView 里分别扮演的角色

136 阅读4分钟

一、Surface 是什么(本质)

  • Surface = 生产者写帧的接口,背后连着一组环形缓冲队列(BufferQueue)。

  • 生产者(Producer) :MediaCodec/MediaPlayer(ExoPlayer)/OpenGL(EGL)/Canvas 等,将图像帧“queue”进 Surface。

  • 消费者(Consumer) :从队列取帧并显示或再处理。

    • SurfaceView 场景下,消费者是 SurfaceFlinger(系统合成器,独立图层)。

    • TextureView 场景下,消费者是 SurfaceTexture(把帧喂给 GPU 纹理,再参与应用内的 View 合成)。

简图:

Producer(解码/绘制/GL/Canvas) → Surface(Producer端) → [BufferQueue] → Consumer
  |                                                              |
  |-- MediaCodec/ExoPlayer/GL/Canvas                             |-- SurfaceFlinger (SurfaceView)
                                                                 |-- SurfaceTexture -> OpenGL纹理 (TextureView)

二、Surface 在 SurfaceView 与 TextureView 中的角色

1) SurfaceView:独立图层,系统级合成

  • 层级:SurfaceView 持有一个独立的 Surface(独立 BufferQueue、独立“图层”),由 SurfaceFlinger 直接拿帧合成到屏幕。

  • 性能:路径短、少一次应用内采样,延迟更低、功耗更优;适合视频播放、相机预览、直播、DRM。

  • 限制:不参与普通 View 的绘制栈,复杂裁剪/圆角/透明/动画支持有限(Android 8.0 后改进了滚动/裁剪,但仍不如 TextureView 灵活)。

  • 回调:通过 SurfaceHolder.Callback 通知生命周期(created/changed/destroyed)。

  • 绑定方式:把播放器/解码器的输出 直接绑定到 holder.surface

2) TextureView:应用内纹理,灵活动画

  • 层级:TextureView 内部持有一个 SurfaceTexture(消费者) 。你把帧写入一个由它包装的 Surface:Surface(textureView.surfaceTexture)。

  • 合成:SurfaceTexture 把帧作为 OpenGL 纹理供 ViewRoot 渲染,参与普通 View 合成,因此支持平移/缩放/旋转/透明/圆角/裁剪/动画。

  • 成本:比 SurfaceView 多一次“纹理采样”和应用内合成,延迟略高/功耗略高,依赖 硬件加速

  • 回调:TextureView.SurfaceTextureListener(available/sizeChanged/destroyed/updated)。

  • 绑定方式:player.setSurface(Surface(textureView.surfaceTexture))。

三、典型使用方式(三种渲染路径)

A. 播放器/解码器 → Surface(最常见)

SurfaceView

class MySV : SurfaceView(context), SurfaceHolder.Callback {
    private val player = /* ExoPlayer 或 MediaCodec 封装 */
    init { holder.addCallback(this) }
    override fun surfaceCreated(h: SurfaceHolder) {
        player.setSurface(h.surface)     // 绑定输出
        player.playWhenReady = true
    }
    override fun surfaceDestroyed(h: SurfaceHolder) {
        player.setSurface(null)          // 解绑再销毁,防泄漏/崩溃
    }
}

TextureView

class MyTV : TextureView(context), SurfaceTextureListener {
    private val player = /* ExoPlayer 或 MediaCodec 封装 */
    init { surfaceTextureListener = this; isOpaque = true }
    override fun onSurfaceTextureAvailable(st: SurfaceTexture, w: Int, h: Int) {
        player.setSurface(Surface(st))
        player.playWhenReady = true
    }
    override fun onSurfaceTextureDestroyed(st: SurfaceTexture): Boolean {
        player.setSurface(null)
        return true // 让系统回收 SurfaceTexture
    }
}

Media3/ExoPlayer 也可用内置控件 StyledPlayerView,通过属性 app:surface_type="surface_view" 或 "texture_view" 一键切换。

B. Canvas 直接绘制(SurfaceView 常用)

val canvas = holder.lockCanvas()
try { /* draw on canvas... */ }
finally { if (canvas != null) holder.unlockCanvasAndPost(canvas) }

C. OpenGL/EGL 直接渲染(两者都可)

  • 用目标 Surface(来自 holder.surface 或 Surface(surfaceTexture))创建 EGL window surface,GL 渲染后 eglSwapBuffers()。

四、时序 & 生命周期要点

  • 创建:surfaceCreated() / onSurfaceTextureAvailable() → 可安全绑定输出。

  • 尺寸变化:surfaceChanged() / onSurfaceTextureSizeChanged() → 处理拉伸/裁剪策略。

  • 销毁:surfaceDestroyed() / onSurfaceTextureDestroyed() → 先 setSurface(null) 再释放解码器/播放器

  • Buffering:通常双/三缓冲;SurfaceView 少一次应用内 draw,TextureView 要在应用内采样为纹理。

  • VSync/合成:SurfaceView 走系统合成,TextureView 受 View 树渲染节奏影响更明显。

五、比例适配/裁剪

SurfaceView

  • 不可靠依赖父布局裁剪,推荐播放器侧或 AspectRatioFrameLayout 控制(Media3 自带)。

TextureView

  • 使用 setTransform(Matrix) 做 CenterCrop/CenterInside 等:
fun TextureView.centerCrop(videoW: Int, videoH: Int) {
    val vw = width.toFloat(); val vh = height.toFloat()
    val s = maxOf(vw / videoW, vh / videoH)
    val dx = (vw - videoW * s) / 2f
    val dy = (vh - videoH * s) / 2f
    transform = Matrix().apply { setScale(s, s); postTranslate(dx, dy) }
}

六、安全/截图/遮挡

  • DRM/Secure:SurfaceView 更稳,可 holder.setFixedSize() / setSecure(true)(或 Window FLAG_SECURE)阻止录屏/截图。

  • 截图差异:SurfaceView 不参与应用内 View.draw();应用内截图通常“截不到”它内容(系统录屏可以)。TextureView 则与普通 View 一样可截图/过渡动画。

  • Z 序:SurfaceView 是独立图层,很多机型上默认盖在普通 View 上;可用 setZOrderMediaOverlay(true) 控制。TextureView 遵循普通 View 层级。

七、选型结论

  • 追求低延迟/长时间播放/相机/DRM → 选 SurfaceView

  • 需要视图级动效/裁剪/圆角/透明/Compose 动画 → 选 TextureView

  • 不确定时做抽象接口(setSurface(Surface?)),以便运行时切换。

八、常见坑 & 最佳实践

  1. 释放顺序:销毁时先解除绑定(setSurface(null))再 release() 播放器/编解码器,避免 NPE/非法状态。
  2. TextureView 硬件加速:必须开启;禁用会黑屏/掉帧。
  3. 比例适配:SurfaceView 不指望父布局裁剪;TextureView 用 setTransform。
  4. 黑屏/花屏:个别机型 TextureView + 某些解码器组合不稳 → 切到 SurfaceView 验证。
  5. onSurfaceTextureDestroyed 返回值:大多数场景返回 true(交给系统回收)。返回 false 代表你要复用/自行释放它,管理复杂且易泄漏。
  6. 功耗:大面积持续播放/相机 → SurfaceView 更省电。
  7. Compose:用 AndroidView 包装 StyledPlayerView;需要动效就让 surface_type=texture_view。

一句话记忆

  • Surface 是“写帧口”;
  • SurfaceView 里,它的帧直接交给 SurfaceFlinger 做系统合成(快);
  • TextureView 里,它的帧先进入 SurfaceTexture,作为纹理在应用内合成(灵活)。