SurfaceView 和 TextureView 都继承 View,在某种程度上与普通的 View 类似,因为它们都是 Android 中的视图组件,用于在屏幕上展示内容。但是,它们还是有些区别滴,下面就一起来探索一下吧!
Surface
官方对 Surface 的解释是:由屏幕合成器管理的原始缓冲区上的句柄,原生缓冲器是用于保存当前窗口的像素数据的,也就是说,通过 Surface 可以获取原生缓冲器以及其中的内容。Surface 对应一块屏幕缓冲区,每个 Window 对应一个 Surface,任何 View 都画在 Surface 上,Surface 中的 Canvas,是用于提供画图的地方。
SurfaceView
SurfaceView 与普通的 View 不同,它拥有自己的 Surface,它的工作方式是创建一个区别于应用窗口的新窗口,与宿主窗口分离,可以在单独线程中处理业务,不受 View 的属性控制,无法进行平移缩放等转换,它是通过“双缓冲”机制来达到高效的界面刷新效果。
双缓冲技术是把要处理的图片在内存中处理好之后,再将其显示在屏幕上。双缓冲主要是为了解决反复局部刷屏带来的闪烁。把要画的东西先画到一个内存区域里,然后整体的一次性画出来。
下面就用 SurfaceView 来做一个简易的画板
class MySurfaceView : SurfaceView, SurfaceHolder.Callback {
private val mPath = Path()
private val mPaint = Paint()
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
init()
}
constructor(context: Context, attributeSet: AttributeSet, defStyle: Int) : super(
context,
attributeSet,
defStyle
) {
init()
}
override fun onTouchEvent(event: MotionEvent): Boolean {
val x = event.x
val y = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> mPath.moveTo(x, y)
MotionEvent.ACTION_MOVE -> mPath.lineTo(x, y)
}
draw()
return true
}
private fun init() {
holder.addCallback(this)
isFocusable = true
isFocusableInTouchMode = true
keepScreenOn = true
with(mPaint) {
color = Color.WHITE
strokeWidth = 10f
strokeCap = Paint.Cap.ROUND
style = Paint.Style.STROKE
}
}
private fun draw() {
CoroutineScope(Dispatchers.IO).launch {
val canvas = holder.lockCanvas()
canvas.drawPath(mPath, mPaint)
holder.unlockCanvasAndPost(canvas)
}
}
override fun surfaceCreated(p0: SurfaceHolder) {
// surface 创建后会调用该函数
}
override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
// surface 状态发生变化的时候会调用该函数
}
override fun surfaceDestroyed(p0: SurfaceHolder) {
// surface 销毁时会调用该函数
}
}
效果如下:
TextureView
TextureView 可以说是一个结合了 View 和 SurfaceTexture 的 View 对象,一个可以把内容流作为外部纹理输出在上面的 View,它只能用于开启了硬件加速的窗口,TextureView 不会创建一个独立的窗口,而是像普通的 View 一样,可以进行平移、旋转等动画,TextureView 在Andriod 4.0 之后的 API 中才能使用,不过现在的安卓设备基本不存在 Andriod 4.0 之前的版本了,所以这点不必在意。
TextureView 的使用也比较简单,需要获取到它的 SurfaceTexture,然后就可以以此来渲染了,下面通过一个简易的视频播放的示例,来瞧瞧它是怎么使用的。
val textureView = findViewById<TextureView>(R.id.textureView)
val mediaPlayer = MediaPlayer()
textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(p0: SurfaceTexture, p1: Int, p2: Int) {
with(mediaPlayer) {
setDataSource(GlobalData.videoUrl)
setSurface(Surface(p0))
prepare()
start()
Log.i(tag, "setOnPreparedListener")
setOnPreparedListener {
it.start()
}
}
}
override fun onSurfaceTextureSizeChanged(p0: SurfaceTexture, p1: Int, p2: Int) {
}
override fun onSurfaceTextureDestroyed(p0: SurfaceTexture): Boolean {
mediaPlayer.stop()
mediaPlayer.release()
return true
}
override fun onSurfaceTextureUpdated(p0: SurfaceTexture) {
}
}
SurfaceTexture
SurfaceTexture 是 Surface 和 OpenGL ES 纹理的组合,用于提供输出到 OpenGL ES 纹理的 Surface,和 SurfaceView 不同的是,它对图像流的处理并不直接显示,而是转为 GL 外部纹理,因此可用于图像流数据的二次处理,如 Camera 滤镜,桌面特效等,但是这样会有若干帧的延迟,同时,由于它本身管理 BufferQueue,因此内存消耗也会稍微大一些。比如 Camera 预览数据,变成纹理后就可以通过 SurfaceTexture 交给 TextureView 作为 View 层级中的一个硬件加速层来显示。
SurfaceView 和 TextureView 的区别
绘制机制
- SurfaceView 使用独立的 Surface 来进行绘制,可以在子线程中进行绘制操作,不会阻塞主线程。它适用于需要频繁更新或处理复杂图像的场景,如游戏、视频播放等。
- TextureView 将图像或视频的内容作为纹理映射到视图上,可以在主线程中进行绘制。它适用于需要与其他视图进行交互或进行简单图像显示的场景。
性能
- SurfaceView 由于使用独立的 Surface,可以在子线程中进行绘制,因此在处理复杂图像或频繁更新时,性能相对较好。
- TextureView 在主线程中进行绘制,可能会受到主线程性能的影响,对于复杂的图像或频繁更新的情况,可能会出现性能问题。
显示效果
- SurfaceView 可以提供更好的图像显示效果,特别是在处理视频时,可以避免图像撕裂和闪烁等问题。
- 在某些情况下,TextureView 可能会出现图像闪烁或不完整的情况,特别是在图像内容发生变化时。
使用场景
- SurfaceView 常用于游戏开发,视频播放,实时相机预览等需要高性能和独立绘制的场景。
- TextureView 常用于显示简单的图像,与其他视图进行交互的场景,如图片浏览器、地图应用等。
总的来说,选择使用 SurfaceView 还是 TextureView 取决于具体的需求和场景。如果需要高性能和独立绘制,或者处理复杂的图像或视频,SurfaceView 可能是更好的选择,如果需要与其他视图进行交互或进行简单的图像显示,TextureView 可能更适合。