一. Android渲染知识
1.1 绘制原理
Android系统要求每一帧都要在 16ms 内绘制完成,平滑的完成一帧意味着任何特殊的帧需要执行所有的渲染代码(包括 framework 发送给 GPU 和 CPU 绘制到缓冲区的命令)都要在 16ms 内完成,保持流畅的体验。这个速度允许系统在动画和输入事件的过程中以约 60 帧每秒( 1秒 / 0.016帧每秒 = 62.5帧/秒 )的平滑帧率来渲染。
如果你的应用没有在 16ms 内完成这一帧的绘制,假设你花了 24ms 来绘制这一帧,那么就会出现掉帧的情况。
系统准备将新的一帧绘制到屏幕上,但是这一帧并没有准备好,所有就不会有绘制操作,画面也就不会刷新。反馈到用户身上,就是用户盯着同一张图看了 32ms 而不是 16ms ,也就是说掉帧发生了。
1.2 掉帧
掉帧是用户体验中一个非常核心的问题。丢弃了当前帧,并且之后不能够延续之前的帧率,这种不连续的间隔会容易会引起用户的注意,也就是我们常说的卡顿、不流畅。引起掉帧的原因非常多,比如:
- 花了非常多时间重新绘制界面中的大部分东西,这样非常浪费CPU周期;
- 过度绘制严重,在绘制用户看不到的对象上花费了太多的时间;
- 有一大堆动画重复了一遍又一遍,消耗 CPU 、 GPU 资源;
- 频繁的触发垃圾回收;
1.3 为什么是60Fps?
Android系统要求每一帧都要在 16ms 内绘制完成,那么1秒的帧率就是约 60 帧每秒( 1秒 / 0.016帧每秒 = 62.5帧/秒 ),那为什么要以 60 Fps来作为 App 性能的衡量标准呢?这是因为人眼和大脑之间的协作无法感知到超过 60 Fps的画面更新。
市面上绝大多数Android设备的屏幕刷新频率是 60 HZ。当然,超过 60 Fps 是没有意义的,人眼感知不到区别。24 Fps 是人眼能感知的连续线性的运动,所以是电影胶圈的常用帧率,因为这个帧率已经足够支撑大部分电影画面所要表达的内容,同时能最大限度地减少费用支出。但是,低于 30 Fps 是无法顺畅表现绚丽的画面内容的,此时就需要用到 60 Fps 来达到想要表达的效果。
应用的界面性能目标就是保持 60 Fps,这意味着每一帧你只有 16 ms(1秒 / 60帧率)的时间来处理所有的任务。
1.4 垃圾回收
垃圾回收器是一个在应用运行期间自动释放那些不再引用的内存的机制,常称 GC 。频繁的 GC 也是导致严重性能问题的罪魁祸首之一。
前面提到,平滑的完成一帧意味着所有渲染代码都必须在 16ms 内完成。频繁的 GC 会严重限制一帧时间内的剩余时间,如果 GC 所做的工作超过了那些必须的工作,那么留给应用平滑的帧率的时间就越少。越接近 16ms ,在垃圾回收事件触发的时候,就越容易导致卡顿。
注意,Android4.4 引进了新的 ART 虚拟机来取代 Dalvik 虚拟机。它们的机制大有不同,简单而言:
- Dalvik 虚拟机的 GC 是非常耗资源的,并且在正常的情况下一个硬件性能不错的Android设备也会很容易耗费掉 10 – 20 ms 的时间;
- ART 虚拟机的GC会动态提升垃圾回收的效率,在 ART 中的中断,通常在 2 – 3 ms 间。 比 Dalvik 虚拟机有很大的性能提升;
ART 虚拟机相对于 Dalvik 虚拟机来说的垃圾回收来说有一个很大的性能提升,但 2 – 3 ms 的回收时间对于超过16ms帧率的界限也是足够的。因此,尽管垃圾回收在 Android 5.0 之后不再是耗资源的行为,但也是始终需要尽可能避免的,特别是在执行动画的情况下,可能会导致一些让用户明显感觉的丢帧。
1.5 UI 线程
UI 线程是应用的主线程,很多的性能和卡顿问题是由于我们在主线程中做了大量的工作。所以,所有耗资源的操作,比如 IO 操作、网络操作、SQL 操作、列表刷新等,都应该用后台进程去实现,不能占用主线程,主线程是 UI 线程,是保持程序流畅的关键;
在 Android 5.0 版本里,Android 框架层引入了 “ Render Thread ” ,用于向 GPU 发送实际渲染的操作。这个线程减轻了一些 UI 线程减少的操作。但是输入、滚动和动画仍然在 UI thread,因为 Thread 必须能够响应操作。
1.6 垂直同步
垂直同步是 Android4.1 通过 Project Butter 在 UI 架构中引入的新技术,同期引入的还有 Triple Buffer 和 HWComposer 等技术,都是为提高 UI 的流畅性而生。
一般而言, GPU 的帧速率应高于刷新率,才不会卡顿或掉帧。如果屏幕刷新率比帧速率还快,屏幕会在两帧中显示同一个画面,这种断断续续情况持续发生时,用户将会很明显地感觉到动画的卡顿或者掉帧,然后又恢复正常,我们常称之为闪屏、跳帧、延迟。应用应避免这些帧率下降的情况,以确保 GPU 能在屏幕刷新之前完成数据的获取及写入,保证动画流畅。