4、SurfaceView与TextureView:SurfaceView原理与使用、TextureView原理与使用、两者对比与选型
做音视频开发,绕不开两个核心的显示组件:SurfaceView 和 TextureView。
我记得刚入行那会儿,领导丢给我一个播放器需求,我二话不说就用 SurfaceView 搞定了。结果产品经理跑过来说:“我要在视频上盖一个弹幕层,还要做旋转动画。” 我当时就懵了。嗯,这就是我今天要跟你聊的——这两个家伙到底有什么区别,什么时候该用谁。
一句话总结:SurfaceView 是“独立窗口”,性能好但难做动画;TextureView 是“普通 View”,灵活但费性能。
4.1 SurfaceView 原理与使用
4.1.1 原理:它其实是个“小窗口”
SurfaceView 的工作原理,说白了就是:它在 View 层级里挖了一个洞,然后在这个洞后面单独开了一个 Surface(绘图表面)。这个 Surface 由 WMS(WindowManagerService)管理,拥有独立的合成层。
为什么会这样?因为 Android 的 View 绘制是在主线程的 UI 渲染流程里走的。如果你直接在 View 上画视频帧,主线程稍微卡一下,画面就掉帧了。SurfaceView 绕开了这个限制——它自己开了一个子线程去渲染,跟主线程的 UI 绘制互不干扰。
我在项目中遇到过一个问题:用 SurfaceView 播放视频时,想在视频上显示一个自定义的进度条。结果发现进度条总是被视频画面盖住,或者闪烁。后来才明白,SurfaceView 的 Z 轴顺序是独立的,它默认在所有普通 View 之上。你需要用 setZOrderOnTop(false) 或者 setZOrderMediaOverlay(true) 来调整层级。
避坑指南:SurfaceView 的“洞”是异步创建的。你调用 getHolder() 拿到 SurfaceHolder 后,Surface 可能还没准备好。一定要在 surfaceCreated() 回调里才开始渲染。我曾经因为没等这个回调,直接往 Surface 上画图,结果黑屏了半小时才找到原因。
4.1.2 使用:三步走
使用 SurfaceView 其实很简单,核心就是跟 SurfaceHolder 打交道。我习惯把它拆成三步:
- 获取 SurfaceHolder:通过
getHolder()拿到。 - 注册回调:实现
SurfaceHolder.Callback,监听创建、变化、销毁。 - 在回调里干活:在
surfaceCreated里启动渲染线程,在surfaceDestroyed里停止。
// 典型用法
SurfaceView surfaceView = findViewById(R.id.surface_view);
SurfaceHolder holder = surfaceView.getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
// Surface 已就绪,可以开始渲染
startRendering(holder.getSurface());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 尺寸或格式变了,更新渲染参数
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface 即将销毁,停止渲染
stopRendering();
}
});
注意:渲染线程一定要在 surfaceCreated 里启动,在 surfaceDestroyed 里停止。不要在主线程里直接画,否则 SurfaceView 的优势就没了。
4.2 TextureView 原理与使用
4.2.1 原理:它就是个“普通 View”
TextureView 跟 SurfaceView 完全不同。它没有独立的窗口,而是作为一个普通的 View 存在于 View 层级中。它内部维护了一个 SurfaceTexture,用来接收视频帧,然后通过硬件加速渲染到 View 上。
你想想看,既然它是普通 View,那它就能享受 View 系统的一切待遇:可以平移、旋转、缩放、做动画,甚至可以用 setAlpha() 控制透明度。这就是为什么弹幕、字幕、UI 叠加层在 TextureView 上可以完美工作。
但代价是什么?性能。TextureView 的每一帧都要经过 View 的渲染管线,这意味着它比 SurfaceView 多了一次纹理上传和合成的开销。我在项目中做过对比,同样 1080p 的视频,TextureView 的 GPU 占用比 SurfaceView 高了 15%-20%。
个人经验:如果你要做视频编辑或者特效处理,TextureView 是更好的选择。因为它可以通过 getBitmap() 方法直接截取当前帧,方便做滤镜、截图等操作。SurfaceView 想截图?嗯,你得用 PixelCopy 或者自己读显存,麻烦得很。
4.2.2 使用:也是三步走
TextureView 的使用稍微有点不同,因为它没有 SurfaceHolder,而是直接操作 SurfaceTexture。
- 创建 TextureView:可以在 XML 里声明,也可以代码 new。
- 设置 SurfaceTextureListener:监听 SurfaceTexture 的可用状态。
- 在回调里绑定 MediaPlayer 或 Camera:把 SurfaceTexture 传过去。
// 典型用法
TextureView textureView = findViewById(R.id.texture_view);
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
// SurfaceTexture 已就绪,可以绑定播放器
mediaPlayer.setSurface(new Surface(surface));
mediaPlayer.start();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
// 尺寸变化,调整播放器参数
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
// 返回 true 表示释放 SurfaceTexture
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// 每帧更新时回调,可以用来做帧率统计
}
});
注意:onSurfaceTextureDestroyed 的返回值很关键。返回 true 表示让 TextureView 自己释放 SurfaceTexture;返回 false 表示你还要继续用。我建议大部分场景返回 true,除非你有特殊需求。
4.3 两者对比与选型
好了,原理和用法都讲完了。现在我们来做个对比,看看什么时候该用谁。
| 对比维度 | SurfaceView | TextureView |
|---|---|---|
| 渲染方式 | 独立 Surface,单独合成 | 通过 View 渲染管线合成 |
| 性能 | 高,延迟低 | 中等,多一次纹理上传 |
| 动画支持 | 差,不能做平移/旋转/缩放 | 好,支持所有 View 动画 |
| 层级控制 | 麻烦,需要 setZOrderOnTop | 自然,跟普通 View 一样 |
| 截图 | 困难,需要 PixelCopy | 简单,直接 getBitmap() |
| 内存占用 | 低 | 高(需要额外纹理缓冲区) |
| API 级别 | API 1 就有 | API 14 引入 |
我个人习惯的选型原则是这样的:
- 纯视频播放,没有复杂 UI 叠加:用 SurfaceView。性能好,省电,延迟低。
- 需要弹幕、字幕、UI 控件覆盖在视频上:用 TextureView。省去层级调整的麻烦。
- 要做视频编辑、滤镜、特效:用 TextureView。方便截图和纹理操作。
- 低端机或追求极致性能:用 SurfaceView。TextureView 在低端机上容易掉帧。
我曾经踩过一个坑:在一个直播项目中,我用了 TextureView 来做连麦画面的显示。结果在部分华为手机上,画面延迟比 SurfaceView 高了 100ms 以上。后来换成 SurfaceView,延迟降下来了,但 UI 叠加层又得重新调层级。嗯,这就是典型的“没有银弹”——你得根据场景做取舍。
4.4 知识体系图
下面我用一张图来总结本章的核心逻辑,帮你理清思路:
这张图把两个组件的核心差异和选型建议都串起来了。你记住一句话就行:SurfaceView 是性能优先,TextureView 是灵活优先。没有绝对的好坏,只有合不合适的场景。
好了,这一章的内容就到这里。希望你在实际项目中能根据需求做出正确的选择。如果遇到什么坑,欢迎随时交流。