一、核心概念
-
SurfaceView :单独的 Surface,独立于 View 层级 ,由 SurfaceFlinger 直接合成,低延迟、性能好 ,但几乎不受 View 变换/裁剪影响。
-
TextureView :在 View 层级里,用 SurfaceTexture 作为内容源,支持平移/缩放/旋转/透明/圆角/动画 ,但性能略差 、需要硬件加速。
二、差异速览表
维度 | SurfaceView | TextureView |
---|---|---|
合成路径 | 独立 Surface → SurfaceFlinger 合成 | 作为普通 View 纹理参与 View 树合成 |
性能/延迟 | ✅ 更低延迟、CPU/GPU 压力更小(适合视频/相机) | ❌ 略高;受 View 合成影响 |
变换/动画 | ❌ 基本不支持 scale/rotate/clip(位置可移动) | ✅ 支持任意动画、透明、圆角、裁剪 |
硬件加速 | 不依赖(但底层通常硬件合成) | 必须开启硬件加速 |
Z 顺序/遮挡 | 在很多机型上盖在普通 View 之上(或通过 setZOrder* 控制) | 与普通 View 一样按层级排序 |
截图/录屏 | 不参与 View 的 draw();系统级录屏一般能抓到(但应用内截图抓不到它的内容) | 参与 View 渲染,应用内截图/过渡动画可见 |
DRM/安全播放 | 更稳(部分设备/方案偏好 secure surface) | 某些 L1 场景可能不被允许(看设备/DRM实现) |
适合场景 | 视频播放、相机预览、直播、低延迟渲染 | 需要变换/蒙版/动效(相册封面、聊天室小窗) |
注:从 Android 8.0 起,SurfaceView 的滚动/裁剪行为比老版本更"像"普通 View,但依旧不支持复杂变换。
三、选型建议(决策树)
-
要做动效/缩放/旋转/圆角/矩形外裁剪? → 选 TextureView
-
追求更低延迟/更省电/大面积视频/相机预览? → 选 SurfaceView
-
需要安全播放(DRM)或遇到黑屏兼容问题? → 优先 SurfaceView
-
要在 Compose 或复杂 View 动画中无缝参与? → 优先 TextureView
四、常用代码骨架
1)SurfaceView(典型视频/相机)
kotlin
class VideoSurfaceView @JvmOverloads constructor(
ctx: Context, attrs: AttributeSet? = null
) : SurfaceView(ctx, attrs), SurfaceHolder.Callback {
private var player: MediaPlayer? = null // 或 ExoPlayer/MediaCodec
init { holder.addCallback(this) }
override fun surfaceCreated(h: SurfaceHolder) {
// 1. 绑定 Surface
player?.setSurface(h.surface)
// 2. 开始/恢复播放
player?.start()
}
override fun surfaceChanged(h: SurfaceHolder, format: Int, w: Int, h: Int) {
// 可在此调整画面裁剪/拉伸策略(通过视频层面适配)
}
override fun surfaceDestroyed(h: SurfaceHolder) {
// 解绑,避免野指针
player?.setSurface(null)
}
}
要点
-
不要对 SurfaceView 做复杂动画;如需覆盖/悬浮,使用 setZOrderOnTop(true) 或 setZOrderMediaOverlay(true)(谨慎使用)。
-
XML/布局裁剪对它并不可靠;用视频端(渲染矩阵)处理比例。
2)TextureView(需要动画/裁剪)
kotlin
class VideoTextureView @JvmOverloads constructor(
ctx: Context, attrs: AttributeSet? = null
) : TextureView(ctx, attrs), TextureView.SurfaceTextureListener {
private var player: MediaPlayer? = null // 或 ExoPlayer/MediaCodec
init { surfaceTextureListener = this; isOpaque = true } // isOpaque=true 更省
override fun onSurfaceTextureAvailable(st: SurfaceTexture, w: Int, h: Int) {
player?.setSurface(Surface(st))
player?.start()
}
override fun onSurfaceTextureSizeChanged(st: SurfaceTexture, w: Int, h: Int) { }
override fun onSurfaceTextureDestroyed(st: SurfaceTexture): Boolean {
player?.setSurface(null)
return true // 我们不复用 st
}
override fun onSurfaceTextureUpdated(st: SurfaceTexture) { }
}
要点
- 需要 硬件加速(确保 Activity/Window 未禁用)。
- 可以通过 matrix 做缩放/平移适配填充(例如 CenterCrop):
scss
fun TextureView.applyCenterCrop(videoW: Int, videoH: Int) {
val viewW = width.toFloat(); val viewH = height.toFloat()
val scale = maxOf(viewW / videoW, viewH / videoH)
val scaledW = videoW * scale; val scaledH = videoH * scale
val dx = (viewW - scaledW) / 2f; val dy = (viewH - scaledH) / 2f
val m = Matrix().apply { setScale(scale, scale); postTranslate(dx, dy) }
setTransform(m)
}
3)ExoPlayer / Media3 快速切换 Surface 类型
- XML(StyledPlayerView)
xml
<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="200dp"
app:surface_type="surface_view" /> <!-- 或 "texture_view" -->
- Kotlin(旧版 ExoPlayer API 示意)
arduino
playerView.useController = false
// 切到 TextureView:player.setVideoTextureView(textureView)
// 切到 SurfaceView:player.setVideoSurfaceView(surfaceView)
具体方法名按你所用的 ExoPlayer/Media3 版本调整(VideoComponent 接口上有对应的 setVideoSurface*)。
五、常见坑 & 最佳实践
- 黑屏/花屏/绿屏
-
多出现在 TextureView + 部分厂商机,可优先换 SurfaceView 试;或确保硬件加速打开。
-
MediaCodec 渲染到 Surface 的生命周期要对齐:注销前 setSurface(null)。
- 比例适配
-
SurfaceView:通过播放器/渲染端(例如 ExoPlayer AspectRatioFrameLayout)处理;不要指望父布局裁剪。
-
TextureView:用 setTransform(matrix) 控制裁剪/缩放,避免 shader 额外开销。
- 层级/遮挡
- SurfaceView 通常盖在上层(或受 setZOrder* 影响),在其上叠盖 UI 可能被"穿透";确保使用 ...MediaOverlay,把弹幕/控件放在另一个窗口层级或改用 TextureView。
- 录屏/截图
-
应用内对 View 的 draw() 并不会画出 SurfaceView 内容(看起来"截不到"),系统录屏一般可以。
-
DRM/FLAG_SECURE 场景可能禁止录屏/截图,优先 SurfaceView。
- 功耗与帧稳
-
大面积、持续视频/相机 → SurfaceView 更省、更稳。
-
小窗/动效/复杂裁剪 → TextureView 体验更好,但注意掉帧风险(降低分辨率或限制刷新)。
- Compose 中使用
-
用 AndroidView(factory = { StyledPlayerView(...) });需要动效时把 app:surface_type 设置为 texture_view。
-
仅自绘可用 SurfaceView 自定义 View 嵌入到 Compose。
六、简单结论
- "能用 SurfaceView 就用 SurfaceView;要做动画/变换就用 TextureView。"
- 视频/相机/直播 → SurfaceView(性能、时延、兼容性更稳)
- 小窗预览、旋转/缩放、裁剪/圆角、复杂叠加 → TextureView