聊聊 SurfaceView 和 TextureView

1,376 阅读5分钟

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 销毁时会调用该函数
    }

}

效果如下:

11111.gif

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 可能更适合。