3、视频画面渲染组件:AndroidView与Surface

3.1 核心概念:为什么需要 Surface?

在 Compose 中直接绘制视频帧会阻塞 UI 线程,导致掉帧和触控延迟。视频渲染需要独立于 Compose 的图形管线:Surface 作为生产者‑消费者队列,允许解码器或 OpenGL 线程直接将帧送入 BufferQueue,而 Compose 只需在布局中占位。

Android 原生提供 SurfaceView 和 TextureView 两种 Surface 容器:

对比项SurfaceViewTextureView
图层独立 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 结构总览

视频 Surface 渲染架构 Compose UI (@Composable) AndroidView { factory } SurfaceView TextureView Surface (BufferQueue) MediaCodec 解码器 ScaleType 变换矩阵 Surface 由生产者 (MediaCodec) 写入帧, Compose 仅占位 (双缓冲无拷贝)

3.7 部署检查清单

  • [ ] 确认 Manifest 中 hardwareAccelerated="true" 用于 TextureView。
  • [ ] 低 API 设备(<21)使用 SurfaceView 并设置 setFormat(PixelFormat.RGBA_8888)。
  • [ ] 画面适配算法在 onSurfaceChanged 中重新计算,防止横竖屏旋转后变形。
  • [ ] 生产环境使用 SurfaceProvider 封装,避免 Compose 重组过程中 Surface 重复创建。
  • [ ] 集成帧率统计:FrameMetricsAggregator 检测播放画面的掉帧率。