它们都是用来在 Android UI 中显示复杂图形内容的组件,比如视频、相机预览或游戏画面,但实现方式和适用场景有很大不同。如果不了解他们的作用的话,在开发中很难去选择,下面就详细梳理下它们的区别和使用方式及其相关概念。
一、SurfaceView (表面视图)
SurfaceView 的核心思想是创建一个独立的、位于普通视图层级之外的 "表面" (Surface) 。
介绍
继承自 View,它包含两个部分------一个普通的 View 组件,用于在屏幕上占据位置,以及一个独立的 Surface。这个 Surface 拥有自己的绘图缓冲区,它不是在主 UI 线程上绘制的。因此,可以在一个单独的后台线程上向这个 Surface 渲染内容,而不会阻塞或影响主 UI 线程的流畅性。
一般的 Activity 包含的多个 View 会组成 View hierachy 的树形结构,只有最顶层的 DecorView,也即跟节点视图,才是对 WMS 可见。这个DecorView在WMS中有一个对应的WindowState。相应地,在SF中对应的Layer。而SurfaceView自带一个Surface,这个Surface在WMS中有自己对应的WindowState,在SF中也会有自己的Layer。虽然在App端它仍在View hierachy中,但在Server端(WMS和SF)中,它与宿主窗口是分离的。这样的好处是对这个Surface的渲染可以放到单独线程去做,渲染时可以有自己的GL context。这对于一些游戏、视频等性能相关的应用非常有益,因为它不会影响主线程对事件的响应。但它也有缺点,因为这个Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中,一些View中的特性也无法使用。
Surface 绘制流程:
- ViewRootImpl 创建
SurfaceControl
,交给 SurfaceFlinger 管理。 - SurfaceView 的 Surface 通过 SurfaceHolder 暴露给开发者。
- 可以用 Canvas 或 OpenGL/MediaCodec 在独立线程绘制内容。
- SurfaceFlinger 直接将该 Surface 合成到屏幕(Overlay 或 GPU 合成)。
Z-Order: SurfaceView 的 Surface 通常会直接在窗口的背景上进行渲染,位于普通 View 的上面。你可以把它想象成一个"浮"在所有常规 View 之上的独立窗口层。
优点:
- 性能优异 :由于渲染发生在独立的线程和独立的表面上,它能高效处理大量、高频率的图形更新,非常适合实时渲染的场景,如视频播放 、相机预览 、游戏渲染。
- 不阻塞主线程:这是它最大的优势。即使你在 Surface 上进行高强度的绘制操作,比如每秒绘制 60 帧,主 UI 线程依然可以保持流畅,不会出现卡顿。
- 硬件加速:通常可以利用硬件加速来提高渲染效率。利用硬件 Overlay,节省 GPU。
- 使用双缓冲机制,播放视频时画面流畅。
缺点:
- 无法变形和动画:因为它是一个独立的 Surface,它不受常规 View 属性的影响。无法像对待普通 View 一样对它进行旋转、缩放、透明度动画或平移。它基本上就是一个矩形区域。
- 层级限制:它总是位于常规 View 的上方,这可能导致一些 UI 布局上的问题。你无法在 SurfaceView 上面放置另一个 View,除非你调整它的 Z-Order(但这比较复杂)。
- 生命周期管理复杂 :需要特别关注它的生命周期,例如在
surfaceCreated()
中开始渲染,在surfaceDestroyed()
中停止渲染线程。 - 透明处理有兼容性问题。
SurfaceView 中的双缓冲机制
什么是双缓冲?
想象一下你在画一幅画,如果直接在展示给观众的画板上作画,观众就会看到你作画的每一个笔触和修改过程,画面会不停闪烁、不完整,体验非常差。
双缓冲机制就是为了解决这个问题。它提供了两个"画板":
- 后台缓冲区(Back Buffer) :这是一个你看不见的、位于内存中的画板。你所有的绘制操作,比如画线条、填充颜色、贴图等,都发生在这个缓冲区里。
- 前台缓冲区(Front Buffer) :这是正在屏幕上显示给用户看的画板。
双缓冲的工作流程大致如下:
- 锁定画布(
lockCanvas()
) :
当你的渲染线程需要开始绘制时,会调用 lockCanvas()
方法。这个方法会返回一个 Canvas
对象。这个 Canvas
对象实际上是与后台缓冲区关联的。此时,前台缓冲区的内容仍然在屏幕上显示,用户看到的还是上一帧的画面。
- 在后台绘制:
现在,你可以在这个 Canvas
上进行任意的绘制操作,比如 drawBitmap()
、drawText()
、drawRect()
等。所有的这些操作都只在后台缓冲区中进行,屏幕上的画面不会有任何变化。这确保了用户看到的画面始终是完整的、稳定的。
- 提交并解锁画布(
unlockCanvasAndPost(canvas)
) :
当你完成所有的绘制操作后,会调用 unlockCanvasAndPost(canvas)
方法。这个操作非常关键,它会做两件事:
- 缓冲区交换(Buffer Swap) :将绘制完成的后台缓冲区 和正在显示给用户的前台缓冲区进行快速交换。这个操作通常由硬件完成,速度非常快。
- 显示新画面:交换完成后,原本的后台缓冲区现在成了前台缓冲区,它的内容立即在屏幕上显示出来。而原来的前台缓冲区则成了新的后台缓冲区,等待下一帧的绘制。
在运用时可以理解为:SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储的是上一次更改前的视图,当使用lockCanvas()获取画布时,得到的实际上是backCanvas而不是正在显示的frontCanvas,之后你在获取到的backCanvas上绘制新视图,再unlockCanvasAndPost(canvas)此视图,那么上传的这张canvas将替换原来的frontCanvas作为新的frontCanvas,原来的frontCanvas将切换到后台作为backCanvas。
解决闪烁问题
如果没有双缓冲,你会直接在屏幕缓冲区上进行绘制。当你在绘制一个复杂图形(比如一个动画帧)时,绘制过程可能需要一段时间。在这个过程中,用户会看到部分旧画面和部分新画面混合在一起,导致画面出现闪烁(flicker) 或 撕裂(tearing) 。
双缓冲机制通过将绘制过程与显示过程完全分离,确保了用户看到的永远是完整的、已经绘制好的画面。整个画面更新是一个"原子操作":在极短的时间内,整个画板被替换,而不是一点一点地修改。
应用场景
上面优点中已经简单描述了应用场景:视频播放 、相机预览 、游戏渲染 等,拿相机举例,Android 的相机 API (Camera
或 Camera2
)可以直接将预览数据流传输到一个 Surface 上,而 SurfaceView 正好提供了这个 Surface。这是一种高效的硬件加速路径。
它们之间都有一个共同的特点:数据流是连续的,需要持续更新, 而普通 View 的 onDraw()
方法需要经过复杂的测量、布局和绘制流程,每次重绘的开销比 SurfaceView 要大得多,并且 渲染时没有双缓冲机制,如果绘制复杂图形,用户可能会看到绘制的中间过程,导致画面闪烁等等...这一系列缺点就让 SurfaceView 在这些场景下成为了唯一的选择!
如果你不知道什么时候使用它,就只要记住几个关键点即可:持续、高频率、高性能渲染的场景。
现在想想之前做过一个运动类的 VIew,需要频繁刷新运动距离,使用普通 View 一帧一帧来刷,一卡一卡的就有点搞笑,还一直优化 onDraw 中的代码耗时,以为那就是性能的极限了,没想到那只是我的极限:)
使用示例
布局代码:
xml
<SurfaceView
android:id="@+id/waveformSurfaceView"
android:layout_width="0dp"
android:layout_height="250dp"
android:layout_margin="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
核心代码,Activity 包含 SurfaceHolder.Callback
回调 和一个用于绘制的内部类 DrawThread
:
kotlin
class SurfaceViewActivity : BaseActivity<BaseViewModel, ActivitySurfaceViewBinding>(), SurfaceHolder.Callback {
private lateinit var waveformSurfaceView: SurfaceView
private var drawThread: DrawThread? = null
override fun initView(savedInstanceState: Bundle?) {
waveformSurfaceView = mBind.waveformSurfaceView
val holder = waveformSurfaceView.holder
holder.addCallback(this)
}
override fun surfaceCreated(holder: SurfaceHolder) {
// 启动绘图线程
drawThread = DrawThread(holder)
drawThread?.start()
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
// 尺寸改变时可处理,此处暂不需要
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
// 停止绘图线程
val thread = drawThread
thread?.setRunning(false)
thread?.join()
drawThread = null
}
/**
* 绘图线程:在 Surface 上绘制动态正弦波
*/
private class DrawThread(private val surfaceHolder: SurfaceHolder) : Thread() {
private var isRunning = true
private val paint = Paint().apply {
color = Color.CYAN
style = Paint.Style.STROKE
strokeWidth = 5f
}
private val backgroundPaint = Paint().apply {
color = Color.DKGRAY
}
fun setRunning(running: Boolean) {
isRunning = running
}
override fun run() {
var time = 0L
var canvas: Canvas? = null
while (isRunning) {
time++
canvas = null
try {
// 锁定画布
canvas = surfaceHolder.lockCanvas() ?: continue
val width = canvas.width
val height = canvas.height
// 绘制背景
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), backgroundPaint)
// 绘制正弦波
var prevX = 0f
var prevY = height / 2f
for (x in 0 until width) {
val angle = (x + time * 5) / (width / 5.0).toFloat() * Math.PI * 2
val y = (height / 2.0 + Math.sin(angle) * (height / 2.0 * 0.8)).toFloat()
canvas.drawLine(prevX, prevY, x.toFloat(), y, paint)
prevX = x.toFloat()
prevY = y
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
if (canvas != null) {
try {
surfaceHolder.unlockCanvasAndPost(canvas)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
}
/**
* 安全地等待线程结束
*/
private fun DrawThread?.join() {
if (this != null && isAlive) {
try {
join()
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
}
}
}
}
运行结果:

gif 看着有点卡,实际还是挺丝滑的。
二、TextureView
TextureView 的实现方式则完全不同,它将内容渲染到一个 OpenGL ES 纹理 (Texture) 中,然后这个纹理被附加到 View 的表面。
介绍
Android 4.0 以后加入,与 SurfaceView 一样继承 View,被绘制在正常的 View 层级中,可以用于实现实时预览等功能。它不是直接绘制内容,而是从一个 SurfaceTexture 中获取图像流,然后将这个图像流作为纹理渲染到自己的 View 表面上,TextureView 必须在硬件加速的窗口中才能工作。
原理:
- SurfaceTexture 本质是一个能接收图像流的 Buffer(生产者-消费者模型)。
- GPU 或 MediaCodec 可以把图像帧渲染到这个 SurfaceTexture。
- TextureView 把 SurfaceTexture 的内容作为纹理(Texture)绘制到 Canvas 中。
- 因为是 View 内部绘制,所以支持缩放、旋转、透明等。
优点:
- 普通 View 特性 :由于它是一个普通的 View,你可以像对待其他 View 一样对它进行操作。例如,可以轻松地对它进行缩放、旋转、透明度等动画,也可以放在其他 View 的上面或下面。
- 更灵活的布局:因为它融入了 View 层级,所以更容易进行布局,可以方便地在上面叠加其他 UI 元素。
- 截图:可以直接对它进行截图操作,这对于需要将视频画面保存为图片等场景非常有用。
缺点:
- 性能开销:它的渲染过程会消耗更多的 GPU 资源,因为内容需要先渲染到纹理,然后再由 View 层级进行绘制。在一些性能要求极高的场景下,可能会比 SurfaceView 稍逊一筹。
- 渲染在主线程:尽管内容源(如视频解码)可以在后台线程进行,但最终的纹理渲染和 View 的绘制过程仍然会影响到主 UI 线程。如果内容更新过于频繁,可能会对 UI 线程造成压力,导致卡顿。
- 硬件加速依赖:它必须依赖于硬件加速,否则将无法正常工作。
- 在5.0以前在主线程渲染,5.0以后有单独的渲染线程。
应用场景
- 需要视频与 UI 混合的播放器(比如视频背景+透明按钮)。
- 视频缩略图、视频特效。
- 需要旋转/缩放/动画的视频组件。
TextureView 使用注意:
- 没 GPU = TextureView 无法工作;
- 即使设备有 GPU,如果关闭了硬件加速 (比如在 AndroidManifest 里
android:hardwareAccelerated="false"
),TextureView 也会失效。- 官方文档明确写了:TextureView 必须依赖硬件加速才能渲染。
使用示例
布局代码:
xml
<TextureView
android:id="@+id/videoTextureView"
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_margin="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/rotateButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rotate"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/videoTextureView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
核心代码。使用 MediaPlayer
来播放视频,点击旋转按钮把视频旋转一圈:
kotlin
class TextureViewActivity : BaseActivity<BaseViewModel, ActivityTextureViewBinding>(), TextureView.SurfaceTextureListener {
private lateinit var videoTextureView: TextureView
private lateinit var rotateButton: Button
private var mediaPlayer: MediaPlayer? = null
override fun initView(savedInstanceState: Bundle?) {
videoTextureView = mBind.videoTextureView
rotateButton = mBind.rotateButton
// 设置 SurfaceTextureListener
videoTextureView.surfaceTextureListener = this
rotateButton.setOnClickListener {
val anim = RotateAnimation(
0f, 360f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f
).apply {
interpolator = LinearInterpolator()
duration = 1500
}
videoTextureView.startAnimation(anim)
}
}
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
mediaPlayer = MediaPlayer().apply {
try {
// 设置数据源:res/raw/sea.mp4
val uri = Uri.parse("android.resource://$packageName/${R.raw.sea}")
setDataSource(this@TextureViewActivity, uri)
// 设置输出到 TextureView 的 SurfaceTexture
setSurface(Surface(surface))
// 异步准备
prepareAsync()
setOnPreparedListener { mp ->
start() // 准备完成后自动播放
}
isLooping = true // 循环播放
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(this@TextureViewActivity, "视频播放失败!", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this@TextureViewActivity, "播放器初始化失败!", Toast.LENGTH_SHORT).show()
}
}
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
// 可处理视频尺寸变化,当前无需特殊处理
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
// 释放 MediaPlayer 资源
mediaPlayer?.let { player ->
if (player.isPlaying) {
player.stop()
}
player.release()
mediaPlayer = null
}
return true // 返回 true,表示我们自行处理了销毁
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
// 每一帧更新时调用,可用于截图或图像处理,此处留空
}
override fun onDestroy() {
super.onDestroy()
// 确保在 Activity 销毁时释放资源
mediaPlayer?.let { player ->
if (player.isPlaying) {
player.stop()
}
player.release()
mediaPlayer = null
}
}
}
运行结果:

三、总结
性能:
指标 | SurfaceView | TextureView |
---|---|---|
渲染延迟 | 低(可直接 Overlay 到屏幕) | 高一些(需要 GPU 纹理合成) |
CPU 占用 | 低(UI 线程无压力) | 略高(UI 线程参与绘制提交) |
GPU 占用 | 低(Overlay 模式)或中(GPU 合成) | 高(所有帧都要 GPU 合成) |
内存占用 | 一般较低 | 稍高(需要额外纹理缓冲) |
选用场景:
SurfaceView:高性能、低延迟、独立线程,缺乏 UI 动画能力。
TextureView:灵活、可混合、支持动画,但性能稍低、延迟稍高。
简单来说,如果你只关心性能,不在乎 UI 效果,用 SurfaceView ;如果你关心 UI 效果和灵活性,对性能要求不是那么极致,用 TextureView。