作者:ByteSaid
链接:https://juejin.cn/post/7156157715230752782
前言
播放视频或者渲染其他的动画的时候,有两个 View 组件可供选择,SurfaceView 和 TextureView,GLSurfaceView 是 SurfaceView 的子类,在 SurfaceView 基础上封装了 EGL 环境管理以及 Render 线程,专用于 3D 游戏开发的视图,这里归类到 SurfaceView 中。在介绍 SurfaceView 与 TextureView 之前,需要先介绍下 Surface。
1 Surface
Surface 就是“表面”的意思,可以简单理解为内存中的一段绘图缓冲区。在 SDK 的文档中,对 Surface 的描述是这样的:“Handle onto a raw buffer that is being managed by the screen compositor”,意思是“由屏幕显示内容合成器(screen compositor)所管理的原生缓冲器的句柄”, 这句话包括下面两个意思:
- 通过 Surface(因为 Surface 是句柄)就可以获得原生缓冲器以及其中的内容。就像在C语言中,可以通过一个文件的句柄,就可以获得文件的内容一样;
- 原生缓冲器(rawbuffer)是用于保存当前窗口的像素数据的。
简单的说 Surface 对应了一块屏幕缓冲区,每个 Window 对应一个 Surface,任何 View 都是画在 Surface 上的,传统的 view 共享一块屏幕缓冲区,所有的绘制必须在 UI 线程中进行。我们不能直接操作 Surface 实例,要通过 SurfaceHolder,在 SurfaceView 中可以通过 getHolder() 方法获取到 SurfaceHolder 实例。 Surface 是一个用来画图形的地方,但是我们知道画图都是在一个 Canvas 对象上面进行的,Surface 中的 Canvas 成员,是专门用于提供画图的地方,就像黑板一样,其中的原始缓冲区是用来保存数据的地方。 Surface 本身的作用类似一个句柄,得到了这个句柄就可以得到其中的Canvas、原始缓冲区以及其他方面的内容,所以简单的说 Surface 是用来管理数据的(句柄)。 说完 surface 就可以说 SurfaceView 了。
2 SurfaceView
2.1 SurfaceView 简介
简单的说 SurfaceView 就是一个有 Surface 的 View,SurfaceView 控制这个 Surface 的格式和尺寸以及绘制位置。传统 View 及其派生类的更新只能在 UI 线程,然而 UI 线程还同时处理其他交互逻辑,这就无法保证 view 更新的速度和帧率了,而 SurfaceView 可以用独立的线程来进行绘制。因此可以提供更高的帧率,例如游戏,摄像头取景等场景就比较适合用 SurfaceView 来实现。 SurfaceView 的核心在于提供了两个线程:UI线程和渲染线程,两个线程通过“双缓冲”机制来达到高效的界面刷新效果。 不用画布,直接在窗口上进行绘图叫做无缓冲绘图。用了一个画布,将所有内容都先画到画布上,在整体绘制到窗口上,就该叫做单缓冲绘图,那个画布就是一个缓冲区。用了两个画布,一个进行临时的绘图,一个进行最终的绘图,这样就叫做双缓冲。 而这个双缓冲可以理解为,SurfaceView 在更新视图时用到了两张 Canvas:
- frontCanvas:实际显示的canvas。
- backCanvas:存储的是上一次更改前的canvas。
当使用 lockCanvas() 获取画布时,得到的实际上是 backCanvas 而不是正在显示的 frontCanvas,之后你在获取到的 backCanvas 上绘制新视图,再 unlockCanvasAndPost(canvas)此视图,那么上传的这张 canvas 将替换原来的 frontCanvas 作为新的 frontCanvas,原来的 frontCanvas 将切换到后台作为 backCanvas。
2.2 SurfaceView 实现机制
SurfaceView 继承自 View,所以它也是一个 View。但是这个 View 和普通的 View 有点不同。SurfaceView 有自己的 Surface,在 Android 中,一个 View 有自己的 Surface,在 WMS 中中就有对应的 WindowState,对应在 SurfaceFlinger 中就有 Layer。
一般的 Activity 包含的多个 View 会组成 View hierachy 的树形结构,只有最顶层的 DectorView 才是对 WMS 可见的,这个 DecorView 在 WMS 中有一个对应的 WindowState,相应的,在 SurfaceFlinger 中有对应的 Layer。而 SurfaceView 正因为它有自己的 Surface,有自己的 Window,它在 WMS 中有对应的 WindowState,在 SurfaceFlinger 中有 Layer。 虽然在 App 端它仍在 View hierachy 中,但在 Server 端(WMS 和 SurfaceFlinger)中,它与宿主窗口是分离的。这样的好处是对这个 Surface 的渲染可以放到单独的线程中去做,渲染时可以有自己的 GL context。这对于一些游戏、视频等性能相关的应用非常有益,因为它不会影响主线程对事件的响应。 但是这也有缺点,因为这个 Surface 不在 View hierachy 中,它的显示也不受 View 的属性控制,所以不能进行平移、缩放等动画,它也不能放在其它 ViewGroup 中,SurfaceView 不能嵌套使用,而且不能使用某些 View 的特性,例如 View.setAlpha()。 从 Android7.0 开始,SurfaceView 的窗口位置与其他 View 渲染同步更新。这意味着在屏幕上平移和缩放 SurfaceView 不会导致渲染失真。
3 TextureView
因为上面所说的 SurfaceView 不在主窗口中,它没法做动画没法使用一些 View 的特性方法,所以在 Android 4.0中引入了 TextureView,它是一个结合了 View 和 SurfaceTexture 的 View 对象。 TextureView 是一个可以把内容流作为外部纹理输出在上面的 View,和 SurfaceView 不同,它不会在 WMS 中单独创建窗口,而是作为 View hierachy 中的一个普通 view,因此它可以和其他普通 View 一样进行平移、旋转、缩放等动画。但是 TextureView 必须在硬件加速的窗口中,它显示的内容流数据可以来自 App 进程或者远程进程。 TextureView 继承自 View,它与其它的 View 一样在 View hierachy 中管理与绘制。TextureView 重载了 draw() 方法,其中主要 SurfaceTexture 中收到的图像数据作为纹理更新到对应的 HardwareLayer 中。 SurfaceTexture.OnFrameAvailableListener 用于通知 TextureView 内容流有新图像到来。SurfaceTextureListener 接口用于让 TextureView 的使用者知道 SurfaceTexture 已准备好,这样就可以把 SurfaceTexture 交给相应的内容源。 Surface 为 BufferQueue 的 Producer 接口实现类,使生产者可以通过它的软件或硬件渲染接口为 SurfaceTexture 内部的 BufferQueue 提供 graphic buffer。 SurfaceTexture 可以用作非直接输出的内容流,这样就提供二次处理的机会。与 SurfaceView 直接输出相比,这样会有若干帧的延迟。同时,由于它本身管理 BufferQueue,因此内存消耗也会稍微大一些。
4 SurfaceTexture
SurfaceTexture 是 Surface 和 OpenGL ES(GLES) 纹理的组合。SurfaceTexture 用于提供输出到 GLES 纹理的 Surface。 SurfaceTexture 是从 Android 3.0(API level 11)开始加入,与 SurfaceView 不同的是,它对图像流的处理并不直接显示,而是转为 GL 外部纹理,因此用于图像流数据的二次处理。 比如 Camera 的预览数据,变成纹理后可以交给 GLSurfaceView 直接显示,也可以通过 SurfaceTexture 交给TextureView 作为 View heirachy 中的一个硬件加速层来显示。 首先,SurfaceTexture 从图像流 (来自 Camera 预览、视频解码、GL 绘制场景等)中获得帧数据,当调用updateTexImage()时,根据内容流中最近的图像更新 SurfaceTexture 对应的 GL 纹理对象。 SurfaceTexture 包含一个应用是其使用方的 BufferQueue。当生产方将新的缓冲区排入队列时,onFrameAvailable() 回调会通知应用。然后,应用调用 updateTexImage(),这会释放先前占有的缓冲区,从队列中获取新缓冲区并执行 EGL 调用,从而使 GLES 可将此缓冲区作为外部纹理使用。
5 SurfaceView 与 TextureView 的对比
项目 | SurfaceView | TextureView |
---|---|---|
内存 | 低 | 高 |
耗电 | 低 | 高 |
绘制 | 及时 | 1-3帧延迟 |
动画和截图 | 不支持 | 支持 |
从性能和安全性角度出发,优先选 SurfaceView,TextureView 是一个不得已的选择:
- 在 Android 7.0 上系统 Surfaceview 的性能比 TextureView 更有优势,支持对象的内容位置和包含的应用内容同步更新,平移、缩放不会产生黑边。 在7.0以下系统如果使用场景有动画效果,可以选择性使用TextureView。
- 由于失效(invalidation)和缓冲的特性,TextureView 增加了额外1~3帧的延迟显示画面更新。
- TextureView 总是使用 GL 合成,而 SurfaceView 可以使用硬件 overlay 后端,可以占用更少的内存。
- TextureView 的内部缓冲队列导致比 SurfaceView 使用更多的内存。
- SurfaceView 内部自己持有 Surface,Surface 创建、销毁、大小改变时系统来处理的,通过 SurfaceHolder 的 callback 回调通知。
- 当画布创建好时,可以将 surface 绑定到 MediaPlayer 中。SurfaceView 如果为用户可见的时候,创建 SurfaceView 的 SurfaceHolder 用于显示视频流解析的帧图片,如果发现 SurfaceView 变为用户不可见的时候,则立即销毁 SurfaceView 的 SurfaceHolder,以达到节约系统资源的目的。
网友评论