帧率
大家都知道安卓显示的帧率为60fps但是为什么呢?
人类视觉系统每秒可处理10到12个图像并单独感知它们,而较高的速率则被视为运动。12fps 大概类似手动快速翻动书籍的帧率,这明显是可以感知到不够顺滑的。24fps 使得人眼感知的是连续线性的运动,这其实是归功于运动模糊的效果。24fps 是电影胶圈通常使用的帧率,因为这个帧率已经足够支撑大部分电影画面需要表达的内容。一般来说 30fps 就是可以接受的,低于 30fps 是无法顺畅表现绚丽的画面内容的,此时将帧率提升至 60fps 则可以明显提升交互感和逼真感,但是一般来说超过 75fps 一般就不容易察觉到有明显的流畅度提升了,所以,超过 60fps 是没有必要的,并且如果帧率超过屏幕刷新率(手机屏幕通常都是 60fps 的刷新率)只会浪费图形处理的能力,因为屏幕不能以这么快的速度更新,这样超过刷新率的帧率就浪费掉了。
Android 在设计的时候,把帧频限定在了每秒 60 帧,当我们的 APP 的帧频 60fps 时,画面就会非常的流畅。但是通常由于各种各样的原因,帧频很可能会小于 60fps,这样就会出现丢帧现象,用户端表现为可感知的卡顿等现象。那面我们的帧频可以高于 60fps 吗,答案是否定的,这是因为界面刷新渲染依赖底层的 VSYNC 信号,VSYNC 信号以每秒 60 次的频率发送给上层,并且高于 60fps 的帧频也是没有必要的,因为人眼与大脑之间的协作无法感知超过 60fps 的画面更新。
开发 app 的性能目标就是保持 60fps,这意味着每一帧你只有 1000/60=16.67ms 的时间来处理所有的任务。如果 16ms 内没有办法把这一帧的任务执行完毕,就会发生丢帧的现象。丢帧越多,用户感受到的卡顿情况就越严重。
卡顿原因
大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能,也就是 16ms 内没有办法把这一帧的任务执行完毕,发生了丢帧的现象。从设计师和产品的角度,他们希望 App 能够有更多的动画、图片等时尚元素来实现流畅的用户体验。但是 Android 系统很有可能无法及时完成那些复杂的界面渲染操作。Android 系统每隔 16ms 发出 VSYNC 信号,触发对 UI 进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的 60fps,为了能够实现 60fps,这意味着程序的大多数操作都必须在 16ms 内完成。
如果 App 的某个操作花费时间是 24ms,系统在得到 VSYNC 信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在 32ms 内看到的会是同一帧画面。
通常,越复杂的 UI,越容易导致卡顿,例如,用户容易在 UI 执行动画或者滑动 ListView 的时候感知到卡顿不流畅,是因为这里的操作相对复杂,容易发生丢帧的现象,从而感觉卡顿。有很多原因可以导致丢帧,也许是因为你的 layout 太过复杂,无法在 16ms 内完成渲染,有可能是因为你的 UI 上有层叠太多的绘制单元,还有可能是因为动画执行的次数过多。这些都会导致 CPU 或者 GPU 负载过重。
具体可见Android 黄油计划和显示刷新机制学习笔记
Android 渲染机制
Android 系统采用一种称为 Surface 的 UI 架构为应用程序提供用户界面。在 Android 应用程序中,每一个 Activity 组件都关联有一个或者若干个窗口,每一个窗口都对应有一个 Surface。有了这个 Surface 之后,应用程序就可以在上面渲染窗口的 UI。最终这些已经绘制好了的 Surface 都会被统一提交给 Surface 管理服务 SurfaceFlinger 进行合成,最后显示在屏幕上面。无论是应用程序,还是 SurfaceFlinger,都可以利用 GPU 等硬件来进行 UI 渲染,以便获得更流畅的 UI。
一句话来概括一下 Android 应用程序显示的过程:Android 应用程序调用 SurfaceFlinger 服务把经过测量、布局和绘制后的 Surface 渲染到显示屏幕上。
成员包括:
ViewRootImpl:用来控制窗口的渲染,以及用来与 WindowManagerService、SurfaceFlinger 通信。
WindowManager:WindowManager 会控制窗口对象,它们是用于容纳视图对象的容器。窗口对象始终由 Surface 对象提供支持。WindowManager 会监督生命周期、输入和聚焦事件、屏幕方向、转换、动画、位置、变形、Z 轴顺序以及窗口的许多其他方面。WindowManager 会将所有窗口元数据发送到 SurfaceFlinger,以便 SurfaceFlinger 可以使用这些数据在屏幕上合成 Surface。
Surface:Android 应用的每个窗口对应一个画布(Canvas),即 Surface,可以理解为 Android 应用程序的一个窗口。Surface 是一个接口,供生产方与使用方交换缓冲区。
SurfaceView:SurfaceView 是一个组件,可用于在 View 层次结构中嵌入其他合成层。SurfaceView 采用与其他 View 相同的布局参数,因此可以像对待其他任何 View 一样对其进行操作,但 SurfaceView 的内容是透明的。当 SurfaceView 的 View 组件即将变得可见时,框架会要求 SurfaceControl 从 SurfaceFlinger 请求新的 Surface。
BufferQueue:BufferQueue 类将可生成图形数据缓冲区的组件(生产方)连接到接受数据以便进行显示或进一步处理的组件(使用方)。几乎所有在系统中移动图形数据缓冲区的内容都依赖于 BufferQueue。
SurfaceFlinger:Android 系统服务,负责管理 Android 系统的帧缓冲区,即显示屏幕。
EGLSurface 和 OpenGL ES:OpenGL ES (GLES) 定义了用于与 EGL 结合使用的图形渲染 API。EGI 是一个规定如何通过操作系统创建和访问窗口的库(要绘制纹理多边形,请使用 GLES 调用;要将渲染放到屏幕上,请使用 EGL 调用)。
Vulkan:Vulkan 是一种用于高性能 3D 图形的低开销、跨平台 API。与 OpenGL ES 一样,Vulkan 提供用于在应用中创建高质量实时图形的工具。
Android 图形架构图
图形系统使用生产者-消费者模式,根据框架图,可以看出基本的操作流程:
图形生产者创建一个 Surface,将图形数据画到 Surface 上。Surface 使用的图形缓冲是通过 Gralloc 来分配的。 图形生产者的窗口信息由 WindowManager 管理,WindowManager 将窗口元数据发送给 SurfaceFlinger 进行合成。 图形消费者处理图形生产者产生的数据。当图形数据用于屏幕显示时,SurfaceFlinger 使用窗口元数据将图形缓冲合成到显示上。图形数据最终渲染到显示设备上是通过 Hardware Composer 完成的。
图像流生产方
图像流生产方可以是生成图形缓冲区以供消耗的任何内容。例如 OpenGL ES、Canvas 2D 和 mediaserver 视频解码器。
图像流消耗方
图像流的最常见消耗方是 SurfaceFlinger,该系统服务会消耗当前可见的 Surface,并使用窗口管理器中提供的信息将它们合成到显示部分。
其他 OpenGL ES 应用也可以消耗图像流,例如相机应用会消耗相机预览图像流。非 GL 应用也可以是使用方,例如 ImageReader 类
应用侧绘制
一个 Android 应用程序窗口里面包含了很多 UI 元素,它们是以树形结构来组织的,父视图包含子视图,一个窗口的根视图是 DecorView 对象,ViewRootImpl 负责与系统进程进行通信。
在绘制一个 Android 应用程序窗口的 UI 之前,我们首先要确定它里面的各个子 UI 元素在父 UI 元素里面的大小以及位置。确定各个子 UI 元素在父 UI 元素里面的大小以及位置的过程又称为测量过程和布局过程。因此,Android 应用程序窗口的 UI 渲染过程可以分为测量、布局和绘制三个阶段,最后生成 Display List。
- 测量:递归(深度优先)确定所有视图的大小(高、宽)
- 布局:递归(深度优先)确定所有视图的位置(左上角坐标)
- 绘制:在画布 canvas 上绘制应用程序窗口所有的视图
- 生成 Display List 数据
测量、布局负责确定每个视图组件的大小和位置信息,接下来就是绘制了。
Android 有两种绘制模型:基于软件的绘制模型和硬件加速的绘制模型。现在默认都是使用硬件加速.
Android 需要把 XML 布局文件转换成 GPU 能够识别并绘制的对象。这个操作是在 DisplayList 的帮助下完成的。Display List 持有所有将要交给 GPU 绘制到屏幕上的数据信息。
关于 Display List 相关内容
Android 需要把 XML 布局文件转换成 GPU 能够识别并绘制的对象。这个操作是在 DisplayList 的帮助下完成的。DisplayList 持有所有将要交给 GPU 绘制到屏幕上的数据信息。
Display List 是一个缓存绘制命令的 Buffer,Display List 的本质是一个缓冲区,它里面记录了即将要执行的绘制命令序列。
Display List 是视图的基本绘制元素,包含元素原始属性(位置、尺寸、角度、透明度等),对应 Canvas 的 drawXxx()方法。
渲染 Display List,发生在应用程序进程的 Render Thread 中。增加 Render Thread 线程,也是为了避免 UI 线程任务过重,用于提高渲染性能。
Display List 是以视图为单位进行构建的,因此每一个视图都对应有一个 Display List。
在某个 View 第一次需要被渲染时,Display List 会因此被创建,当这个 View 要显示到屏幕上时,我们会执行 GPU 的绘制指令来进行渲染。
在绘制窗口的下一帧时,若某一个视图的 UI 没有发生变化,那么就不必执行与它相关的 Canvas API,即不用执行它的成员函数 onDraw,而是直接复用上次构建的 Display List 即可。
在绘制窗口的下一帧时,若某一个视图的 UI 发生了变化,但是只是一些简单属性发生了变化,例如位置和透明度等简单属性,那么也不必重建它的 Display List,而是直接修改上次构建的 Display List 的相关属性即可,这样也可以省去执行它的成员函数 onDraw。
硬件加速条件下,CPU 用于控制复杂绘制逻辑、构建或更新 DisplayList;GPU 用于完成图形计算、渲染 DisplayList。
系统侧渲染
1. Display List 数据交由 GPU 进行渲染处理
Android 需要把 XML 布局文件转换成 GPU 能够识别并绘制的对象。这个操作是在 DisplayList 的帮助下完成的。Display List 持有所有将要交给 GPU 绘制到屏幕上的数据信息。
Display List 是一个缓存绘制命令的 Buffer,Display List 的本质是一个缓冲区,它里面记录了即将要执行的绘制命令序列。渲染 Display List,发生在应用程序进程的 Render Thread 中。增加 Render Thread 线程,也是为了避免 UI 线程任务过重,用于提高渲染性能。
这些绘制命令最终会转化为 Open GL 命令由 GPU 执行。这意味着我们在调用 Canvas API 绘制 UI 时,实际上只是将 Canvas API 调用及其参数记录在 Display List 中,然后等到下一个 VSYNC 信号到来时,记录在 Display List 里面的绘制命令才会转化为 Open GL 命令由 GPU 执行。
2. GPU 渲染处理
Android 使用 OpenGL ES (GLES) API 渲染图形。GPU 将视图栅格化后,生成 Surface。GPU 作为图像流生产方将显示内容,最终发送给图像流的消耗方 SurfaceFlinger。
大多数客户端使用 OpenGL ES 或 Vulkan 渲染到 Surface 上(硬件加速,使用了 GPU 渲染)。但是,有些客户端使用画布渲染到 Surface 上(未使用硬件加速)
3. 生成 Surface 并存储到 BufferQueue
Surface 对象使应用能够渲染要在屏幕上显示的图像。通过 SurfaceHolder 接口,应用可以编辑和控制 Surface。Surface 是一个接口,供生产方与使用方交换缓冲区。
BufferQueue 是 SurfaceFlinger 使用的缓冲区队列,而 Surface 是 BufferQueue 的生产方。BufferQueue 类将可生成图形数据缓冲区的组件(生产方)连接到接受数据以便进行显示或进一步处理的组件(使用方,例如 SurfaceFlinger)。几乎所有在系统中移动图形数据缓冲区的内容都依赖于 BufferQueue。
用于显示 Surface 的 BufferQueue 通常配置为三重缓冲。缓冲区是按需分配的,因此,如果生产方足够缓慢地生成缓冲区(例如在 60 fps 的显示屏上以 30 fps 的速度进行缓冲),队列中可能只有两个分配的缓冲区。按需分配缓冲区有助于最大限度地减少内存消耗。
上图描述了图形管道的流程。左侧为图形生产者,右侧为图形消费者,中间通过 BufferQueues 连接。图中,主屏幕、状态栏和系统界面通过 GPU 渲染生成图形缓冲区,做为生产者传递给 BufferQueues。SurfaceFlinger 做为消费者,接收到 BufferQueues 的通知后,取出可用的图形缓冲区,送给显示端。例图中将状态栏和系统界面的图形缓冲送给 GPU 合成,生成新的图形缓冲后再通过 BufferQueues 发送到硬件混合渲染器。
4. SurfaceFlinger 将显示数据发送给显示屏
SurfaceFlinger 接受来自多个源的数据缓冲区,然后将它们进行合成并发送到显示屏。WindowManager 为 SurfaceFlinger 提供缓冲区和窗口元数据,而 SurfaceFlinger 可使用这些信息将 Surface 合成到屏幕。
SurfaceFlinger 可通过两种方式接受缓冲区:通过 BufferQueue 和 SurfaceControl,或通过 ASurfaceControl。上文中,我们已经介绍了 BufferQueue。ASurfaceControl 是 Android 10 新增的,这是 SurfaceFlinger 接受缓冲区的另一种方式。ASurfaceControl 将 Surface 和 SurfaceControl 组合到一个事务包中,该包会被发送至 SurfaceFlinger。ASurfaceControl 与层相关联,应用可通过 ASurfaceTransactions 更新该层。然后,应用可通过回调(用于传递包含锁定时间、获取时间等信息的 ASurfaceTransactionStats)获取有关 ASurfaceTransactions 的信息。
总结
帧率是以帧为单位的位图图像每秒连续出现在显示器上的次数(速率)。简单来说就是一秒钟,屏幕显示多少张画面。
Android 在设计的时候,把帧频限定在了每秒 60 帧,当我们的 APP 的帧频 60fps 时,画面就会非常的流畅。
界面刷新渲染依赖底层的 VSYNC 信号,VSYNC 信号以每秒 60 次的频率发送给上层,并且高于 60fps 的帧频也是没有必要的,因为人眼与大脑之间的协作无法感知超过 60fps 的画面更新。
大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能,也就是 16ms 内没有办法把这一帧的任务执行完毕,发生了丢帧的现象。
由于 CPU 和 GPU 的设计不同,CPU 更擅长复杂逻辑控制,而 GPU 得益于大量 ALU 和并行结构设计,更擅长数学运算。在 Android 系统中,CPU 与 GPU 的分工不同,CPU 主要负责包括 Measure,Layout,Record,Execute 的计算操作,GPU 主要负责 Rasterization(栅格化)操作。
Android 为了提高视图渲染的性能,在 Android 3.0 中引入了硬件加速。
在 Android 应用程序中,每一个 Activity 组件都关联有一个或者若干个窗口,每一个窗口都对应有一个 Surface。有了这个 Surface 之后,应用程序就可以在上面渲染窗口的 UI。最终这些已经绘制好了的 Surface 都会被统一提交给 Surface 管理服务 SurfaceFlinger 进行合成,最后显示在屏幕上面。无论是应用程序,还是 SurfaceFlinger,都可以利用 GPU 等硬件来进行 UI 渲染,以便获得更流畅的 UI。
一句话来概括一下 Android 应用程序显示的过程:Android 应用程序调用 SurfaceFlinger 服务把经过测量、布局和绘制后的 Surface 渲染到显示屏幕上。
图形生产者创建一个 Surface,将图形数据画到 Surface 上。Surface 使用的图形缓冲是通过 Gralloc 来分配的。 图形生产者的窗口信息由 WindowManager 管理,WindowManager 将窗口元数据发送给 SurfaceFlinger 进行合成。 图形消费者处理图形生产者产生的数据。当图形数据用于屏幕显示时,SurfaceFlinger 使用窗口元数据将图形缓冲合成到显示上。图形数据最终渲染到显示设备上是通过 Hardware Composer 完成的。
Android 需要把 XML 布局文件转换成 GPU 能够识别并绘制的对象。这个操作是在 DisplayList 的帮助下完成的。Display List 持有所有将要交给 GPU 绘制到屏幕上的数据信息。
Display List 是一个缓存绘制命令的 Buffer,Display List 的本质是一个缓冲区,它里面记录了即将要执行的绘制命令序列。渲染 Display List,发生在应用程序进程的 Render Thread 中。增加 Render Thread 线程,也是为了避免 UI 线程任务过重,用于提高渲染性能。
Android 使用 OpenGL ES (GLES) API 渲染图形。GPU 将视图栅格化后,生成 Surface。GPU 作为图像流生产方将显示内容,最终发送给图像流的消耗方 SurfaceFlinger。
BufferQueue 是 SurfaceFlinger 使用的缓冲区队列,而 Surface 是 BufferQueue 的生产方。BufferQueue 类将可生成图形数据缓冲区的组件(生产方)连接到接受数据以便进行显示或进一步处理的组件(使用方,例如 SurfaceFlinger)。
SurfaceFlinger 接受来自多个源的数据缓冲区,然后将它们进行合成并发送到显示屏。WindowManager 为 SurfaceFlinger 提供缓冲区和窗口元数据,而 SurfaceFlinger 可使用这些信息将 Surface 合成到屏幕。