一、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?)),以便运行时切换。
八、常见坑 & 最佳实践
- 释放顺序:销毁时先解除绑定(setSurface(null))再 release() 播放器/编解码器,避免 NPE/非法状态。
- TextureView 硬件加速:必须开启;禁用会黑屏/掉帧。
- 比例适配:SurfaceView 不指望父布局裁剪;TextureView 用 setTransform。
- 黑屏/花屏:个别机型 TextureView + 某些解码器组合不稳 → 切到 SurfaceView 验证。
- onSurfaceTextureDestroyed 返回值:大多数场景返回 true(交给系统回收)。返回 false 代表你要复用/自行释放它,管理复杂且易泄漏。
- 功耗:大面积持续播放/相机 → SurfaceView 更省电。
- Compose:用 AndroidView 包装 StyledPlayerView;需要动效就让 surface_type=texture_view。
一句话记忆:
- Surface 是“写帧口”;
- 在 SurfaceView 里,它的帧直接交给 SurfaceFlinger 做系统合成(快);
- 在 TextureView 里,它的帧先进入 SurfaceTexture,作为纹理在应用内合成(灵活)。