Android 应用框架 UI 基于对象层级结构,该层级结构以 View 开头。所有 UI 元素都经历一系列测量和布局过程,使其能够适应矩形区域。然后,所有可见的视图对象都呈现到 Surface 上,该 Surface 由 WindowManager 在应用转到前台时设置。应用的 UI 线程执行每帧的布局和渲染到缓冲区。
SurfaceView
SurfaceView 是一个组件,您可以使用它在视图层级结构中嵌入额外的合成层。SurfaceView 采用与其他视图相同的布局参数,因此可以像任何其他视图一样进行操作,但 SurfaceView 的内容是透明的。
当您使用外部缓冲区来源(例如 GL 上下文或媒体解码器)进行渲染时,您需要将缓冲区从缓冲区来源复制到屏幕上以显示缓冲区。使用 SurfaceView 可以让您做到这一点。
当 SurfaceView 的视图组件即将变为可见时,框架会请求 SurfaceControl 从 SurfaceFlinger 请求新的 Surface。要接收 Surface 创建或销毁时的回调,请使用 SurfaceHolder 接口。默认情况下,新创建的 Surface 位于应用 UI Surface 的后面。您可以替换默认的 Z 顺序,将新 Surface 放在顶部。
在需要渲染到单独的 Surface 的情况下(例如,当您使用 Camera API 或 OpenGL ES 上下文进行渲染时),使用 SurfaceView 进行渲染是有益的。当您使用 SurfaceView 进行渲染时,SurfaceFlinger 会直接将缓冲区合成到屏幕上。如果不使用 SurfaceView,您需要将缓冲区合成到屏幕外 Surface,然后再将其合成到屏幕上,因此使用 SurfaceView 进行渲染可以消除额外的工作。在使用 SurfaceView 进行渲染后,请使用 UI 线程与 Activity 生命周期进行协调,并在需要时调整视图的大小或位置。然后,硬件合成器会混合应用 UI 和其他层。
新的 Surface 是 BufferQueue 的生产者端,其消费者是 SurfaceFlinger 层。您可以使用任何可以馈送 BufferQueue 的机制来更新 Surface,例如 Surface 提供的 Canvas 函数、附加 EGLSurface 并使用 GLES 在 Surface 上绘制,或配置媒体解码器以写入 Surface。
SurfaceView 和 Activity 生命周期
使用 SurfaceView 时,请从主 UI 线程以外的线程渲染 Surface。
对于具有 SurfaceView 的 Activity,有两个单独但相互依赖的状态机
- 应用
onCreate
/onResume
/onPause
- Surface 创建/更改/销毁
当 Activity 启动时,您将按以下顺序收到回调
onCreate()
onResume()
surfaceCreated()
surfaceChanged()
如果您点击“返回”,您将收到
onPause()
surfaceDestroyed()
(在 Surface 消失之前调用)
如果您旋转屏幕,Activity 将被销毁并重新创建,您将收到完整的周期。您可以通过检查 isFinishing()
来判断它是否是快速重启。Activity 的启动/停止速度可能非常快,以至于 surfaceCreated()
在 onPause()
之后发生。
如果您点击电源按钮以使屏幕变黑,您将仅收到 onPause()
,而没有 surfaceDestroyed()
。Surface 保持活动状态,渲染可以继续进行。如果您继续请求 Choreographer 事件,您可以继续接收它们。如果您有一个强制使用不同方向的锁屏,则当设备取消黑屏时,您的 Activity 可能会重新启动。否则,您可以从屏幕黑屏状态恢复,并保持与之前相同的 Surface。
线程的生命周期可以绑定到 Surface 或 Activity,具体取决于您希望在屏幕变黑时发生什么情况。线程可以在 Activity 启动/停止时或在 Surface 创建/销毁时启动/停止。
在 Activity 启动/停止时启动/停止线程与应用生命周期配合良好。您可以在 onResume()
中启动渲染器线程,并在 onStop()
中停止它。在创建和配置线程时,有时 Surface 已经存在,有时则不存在(例如,在使用电源按钮切换屏幕后,Surface 仍然处于活动状态)。您必须等待 Surface 创建后才能在线程中初始化。您无法在 surfaceCreate()
回调中初始化,因为它在 Surface 未重新创建时不会再次触发。相反,查询或缓存 Surface 状态,并将其转发到渲染器线程。
在 Surface 创建/销毁时启动/停止线程效果很好,因为 Surface 和渲染器在逻辑上是相互关联的。您可以在 Surface 创建后启动线程,这避免了一些线程间通信问题;并且 Surface 创建/更改消息会被简单地转发。为确保在屏幕变黑时渲染停止,并在屏幕取消黑屏时恢复渲染,请告知 Choreographer 停止调用帧绘制回调。onResume()
会在渲染器线程正在运行时恢复回调。但是,如果您基于帧之间经过的时间进行动画处理,则在下一个事件到达之前可能会有很大的间隔;使用显式的暂停/恢复消息可以解决此问题。
无论线程的生命周期绑定到 Activity 还是 Surface,这两种方案都侧重于如何配置渲染器线程以及它是否正在执行。一个相关的考虑因素是在 Activity 被终止时(在 onStop()
或 onSaveInstanceState()
中)从线程中提取状态;在这种情况下,将线程的生命周期绑定到 Activity 效果最佳,因为在渲染器线程已加入后,可以访问渲染线程的状态而无需同步原语。
GLSurfaceView
GLSurfaceView 类提供了用于管理 EGL 上下文、线程间通信以及与 Activity 生命周期交互的帮助程序类。您无需使用 GLSurfaceView 即可使用 GLES。
例如,GLSurfaceView 会创建一个线程用于渲染,并在其中配置 EGL 上下文。当 Activity 暂停时,状态会自动清理。大多数应用无需了解任何关于 EGL 的知识即可将 GLES 与 GLSurfaceView 结合使用。
在大多数情况下,GLSurfaceView 可以简化 GLES 的使用。在某些情况下,它可能会成为障碍。