3、视频画面渲染组件:AndroidView与Surface
3.1 核心概念:为什么需要 Surface?
在 Compose 中直接绘制视频帧会阻塞 UI 线程,导致掉帧和触控延迟。视频渲染需要独立于 Compose 的图形管线:Surface 作为生产者‑消费者队列,允许解码器或 OpenGL 线程直接将帧送入 BufferQueue,而 Compose 只需在布局中占位。
Android 原生提供 SurfaceView 和 TextureView 两种 Surface 容器:
| 对比项 | SurfaceView | TextureView |
|---|---|---|
| 图层 | 独立 Window(Z‑order 可调) | 嵌入 View 树,同层绘制 |
| 硬件加速 | 强制硬件层 | 依赖硬件加速开关 |
| 动画/变换 | 受限(位移、缩放需通过 WindowManager) | 支持 Compose 变换(Modifier) |
| Surface 获取时机 | 回调 surfaceCreated() | surfaceTexture 就绪后可用 |
| 内存开销 | 低(独立 Layer) | 高(需额外 FBO) |
| Compose 推荐 | 纯播放场景(全屏/嵌入列表) | 需随 Compose 变换适配的场景 |
3.2 组件选型与 SurfaceProvider 模式
Compose 通过 AndroidView 提供互操作桥接:
@Composable
fun VideoSurface(
modifier: Modifier = Modifier,
useTextureView: Boolean = false,
onSurfaceReady: (Surface) -> Unit
) {
AndroidView(
modifier = modifier,
factory = { context ->
if (useTextureView) {
TextureView(context).apply {
surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(
surface: SurfaceTexture, width: Int, height: Int
) {
onSurfaceReady(Surface(surface))
}
// 其他回调...
}
}
} else {
SurfaceView(context).apply {
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
onSurfaceReady(holder.surface)
}
// 其他回调...
})
}
}
}
)
}
SurfaceProvider 是封装后的接口,将 Surface 的创建与 Compose 重组解耦:
class SurfaceProvider(
private val useTextureView: Boolean
) : View, SurfaceHolder.Callback, TextureView.SurfaceTextureListener {
private val _surface = MutableStateFlow<Surface?>(null)
val surface: StateFlow<Surface?> = _surface.asStateFlow()
// View 测量、布局...
// 回调中更新 _surface
}
选型建议: 全屏纯播放 → SurfaceView(省电、性能优);需要 Compose 动画、圆角、阴影 → TextureView(灵活但耗内存)。
3.3 画面适配算法
视频帧可能使用不同宽高比(16:9、4:3、2.35:1),播放器需支持 fit、fill、center crop、stretch 四种模式:
| 模式 | 算法 | 效果 |
|---|---|---|
| fit(等比缩放留黑) | 保持比例,完全显示 | 上下/左右有黑边 |
| fill(等比缩放填满) | 保持比例,裁剪超出区域 | 无黑边,边缘可能被裁剪 |
| center crop | 类似 fill,但居中裁剪 | 无黑边,中心保持 |
| stretch(拉伸) | 不保持比例,填满 | 变形 |
在 TextureView 上通过 Matrix 实现:
fun TextureView.applyScaleType(
videoWidth: Int, videoHeight: Int,
mode: ScaleType = ScaleType.FIT
) {
val viewWidth = width.toFloat()
val viewHeight = height.toFloat()
val matrix = Matrix()
when (mode) {
ScaleType.FIT -> {
val scale = minOf(viewWidth / videoWidth, viewHeight / videoHeight)
val dx = (viewWidth - videoWidth * scale) / 2f
val dy = (viewHeight - videoHeight * scale) / 2f
matrix.setScale(scale, scale)
matrix.postTranslate(dx, dy)
}
ScaleType.FILL -> {
val scale = maxOf(viewWidth / videoWidth, viewHeight / videoHeight)
val dx = (viewWidth - videoWidth * scale) / 2f
val dy = (viewHeight - videoHeight * scale) / 2f
matrix.setScale(scale, scale)
matrix.postTranslate(dx, dy)
}
ScaleType.STRETCH -> {
matrix.setScale(viewWidth / videoWidth, viewHeight / videoHeight)
}
// CENTER_CROP 类似 FILL
}
setTransform(matrix)
}
对 SurfaceView,需在 surfaceChanged() 中通过 surfaceView.holder.setFixedSize() 或修改 View 尺寸来实现适配;更多情况下推荐结合 Modifier.aspectRatio(videoWidth/videoHeight) 在 Compose 层完成比例约束。
3.4 调试与常见错误
- 黑屏/无画面: 检查
surfaceCreated是否触发;确认 Surface 已传递给解码器;TextureView 需硬件加速。 - 画面闪烁/撕裂: SurfaceView 在 API 23 以下需设置
holder.setFormat(PixelFormat.TRANSLUCENT)避免闪烁。 - 画面拉伸变形: 视频宽高比与 View 尺寸不匹配;未调用
applyScaleType或setTransform。 - 内存泄漏:
AndroidView的 factory 中创建的SurfaceView/TextureView需在 dispossess 时释放回调引用。
调试工具: 在
OnSurfaceReady 回调中 Log.d(TAG, "surface: $surface, size: ${width}x${height}");使用布局边界调试(开发者选项 → 显示布局边界)确认 View 尺寸。
3.5 性能与稳定性优化
- SurfaceView 双缓冲: 默认硬件合成器管理,避免额外的 CPU 拷贝;仅需关注
onFrameAvailable回调频率。 - TextureView 渲染管线: 若帧率不稳,可降低
SurfaceTexture.setDefaultBufferSize或使用openGL直接渲染。 - 切换动画: 从 SurfaceView 切换到 TextureView(或反之)时,先预创建目标 Surface,通过
Crossfade或AnimatedVisibility平滑过渡,避免黑屏瞬间。 - 低端设备: 强制使用 SurfaceView,并缩小渲染分辨率(如 720p)降低带宽消耗。
3.6 结构总览
3.7 部署检查清单
- [ ] 确认 Manifest 中
hardwareAccelerated="true"用于 TextureView。 - [ ] 低 API 设备(<21)使用
SurfaceView并设置setFormat(PixelFormat.RGBA_8888)。 - [ ] 画面适配算法在
onSurfaceChanged中重新计算,防止横竖屏旋转后变形。 - [ ] 生产环境使用
SurfaceProvider封装,避免 Compose 重组过程中 Surface 重复创建。 - [ ] 集成帧率统计:
FrameMetricsAggregator检测播放画面的掉帧率。