影响流畅性的因素有很多,比如:
- View布局及本身;
- 主线程任务过多,导致绘制任务延迟;
- 内存问题影响绘制;
- 弱网,数据请求过慢,图片加载过慢。
本文从View渲染的角度分析和优化,后续会推出其他几个角度的分析。
流畅性的概念
App运行是否流畅的唯一标准就是app是否达到每秒60帧的平滑的帧速(16ms的帧速)。
16ms是个什么概念?
16ms很长
- 1s=10^3ms
- 而CPU主频按单位GHz算,1秒钟有10^9次方个时钟周期
也就是说:16ms内可以执行一千多万条指令,一条指令往往要执行多个周期实际上这个值要小很多,这里不严谨的计算想告诉大家16ms是一段很长的时间。
16ms也很短
在这16ms里,必须要完成主线程里面的所有任务,包含各类事件的响应、业务逻辑的处理、垃圾回收、UI绘制…先说重中之重的界面绘制。
- 硬件时钟定时发出VSYNC信号:(Refresh Rate:这取决于硬件的固定参数,例如60Hz。)
- 栅格化:CPU拿到UI对象xml文件处理为多维图形,纹理 —–通过OpeGL ES接口调用GPU
- 光栅化:GPU对图进行光栅化(Frame Rate:代表了GPU在一秒内绘制操作的帧数,例如30fps,60fps )
- 投射到屏幕
栅格化
通常来说栅格化就是view的measure、layout再处理成多边形和纹理的过程,这部分是整个流程中最耗时的,在可见性不发生改变的时候这个过程是可以跳过的。
在某个View第一次需要被渲染时,DisplayList会因此而被创建,当这个View要显示到屏幕上时,我们会执行GPU的绘制指令来进行渲染。
- 如果你在后续有执行类似移动这个View的位置等操作而需要再次渲染这个View时,我们就仅仅需要额外操作一次渲染指令就够了。
- 如果你修改了View中的某些可见组件,那么之前的DisplayList就无法继续使用了,我们需要回头重新创建一个DisplayList并且重新执行渲染指令并更新到屏幕上。
GPU会获取图形数据进行渲染,然后硬件负责把渲染后的内容呈现到屏幕上,他们两者不停的进行协作。当系统收到VSYNC信号的时候无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。这样的情况多了,用户就会觉得应用程序超级烂。
下面进入到干货部分,主要从工具和理论两个方面来说明。
工具篇
1. 过度绘制工具 (Show GPU Overdraw)
通过手机设置里面的开发者选项,打开调试GPU过度绘制工具 (Show GPU Overdraw)的选项,可以观察UI上的Overdraw情况。
工具解读
- 蓝色 这部分区域绘制了一次
- 淡绿 这部分区域绘制了两次
- 淡红 这部分区域绘制了三次
- 深红 这部分区域绘制了三次
我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。
Overdraw有时候是因为你的UI布局存在大量重叠的部分,还有的时候是因为非必须的重叠背景。例如某个Activity有一个背景,然后里面的Layout又有自己的背景,同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色Overdraw区域,增加蓝色区域的占比。这一措施能够显著提升程序性能。
2. GPU 呈现模式分析 (Profile GPU Rendering)
通过手机设置里面的开发者选项,打开手机里面的开发者选项,选择GPU 呈现模式分析 (Profile GPU Rendering),选中在屏幕上显示条形图(On screen as bars)的选项。
AS 2.x 的版本应该也是有这个工具的,但是3.0 改成profile之后就没了。
工具解读 (6.0以上的系统工具)
随着界面的刷新,界面上会滚动显示垂直的柱状图来表示每帧画面所需要渲染的时间,柱状图越高表示花费的渲染时间越长。
中间有一根绿色的横线,代表16ms,我们需要确保每一帧花费的总时间都低于这条横线,这样才能够避免出现卡顿的问题。
每一条柱状线都包含三部分,蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间。
竖条区段 | 渲染阶段 | 说明 |
---|---|---|
橙色 | 交换缓冲区 | 表示 CPU 等待 GPU 完成其工作的时间。 如果此竖条升高,则表示应用在 GPU 上执行太多工作。 |
红色 | 命令问题 | 表示 Android 的 2D 渲染器向 OpenGL 发起绘制和重新绘制显示列表的命令所花的时间。 此竖条的高度与它执行每个显示列表所花的时间的总和成正比—显示列表越多,红色条就越高。 |
天蓝色 | 同步和上传 | 表示将位图信息上传到 GPU 所花的时间。 大区段表示应用花费大量的时间加载大量图形。 |
蓝色 | 绘制 | 表示用于创建和更新视图显示列表的时间。 如果竖条的此部分很高,则表明这里可能有许多自定义视图绘制,或 onDraw 函数执行的工作很多。 |
绿色 | 测量/布局 | 表示在视图层次结构中的 onLayout 和 onMeasure 回调上所花的时间。 大区段表示此视图层次结构正在花很长时间进行处理。 |
绿色 | 动画/输入处理 | 表示评估运行该帧的所有动画程序所花的时间。 如果此区段很大,则表示您的应用可能在使用性能欠佳的自定义动画程序,或因更新属性而导致一些意料之外的工作。 |
绿色 | 其他时间/VSync 延迟 | 表示应用执行两个连续帧之间的操作所花的时间。 它可能表示界面线程中进行的处理太多,而这些处理任务本可以分流到其他线程。 |
导出GPU 呈现模式分析结果
使用adb命令可以导出GPU呈现的结果,可以输出最近一些帧的详细帧信息,有了他们我们就可以进行数据化的分析和优化了。
// adb shell dumpsys gfxinfo > 输出路径
// adb shell dumpsys gfxinfo 直接在控制台打印
adb shell dumpsys gfxinfo > test.txt
输出结果
// 输出结果
com.zhy.sample_okhttp/com.zhy.sample_okhttp.MainActivity/android.view.ViewRootImpl@e7194d7 (visibility=0)
Draw Prepare Process Execute
50.00 0.69 22.25 12.80
50.00 0.51 2.36 8.44
2.84 0.29 4.39 15.26
Stats since: 3115548352260ns
Total frames rendered: 92
Janky frames: 11 (11.96%)
90th percentile: 21ms
95th percentile: 61ms
99th percentile: 129ms
Number Missed Vsync: 3
Number High input latency: 0
Number Slow UI thread: 7
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 8
View hierarchy:
com.zhy.sample_okhttp/com.zhy.sample_okhttp.MainActivity/android.view.ViewRootImpl@e7194d7
29 views, 42.37 kB of display lists
Total ViewRootImpl: 1
Total Views: 29
Total DisplayList: 42.37 kB
这里将逐一解释以上重点信息:
- Draw:测量绘制Display List的时间,也就是measure和layout的时间。
- Prepare:标示准备时间
- Process:OpenGL渲染Display的时间
- Execute:CPU等待GPU处理的时间,主线程被其他任务占用的时间,比如:处理业务逻辑之类的
- Graphics info for pid 31148 [com.android.settings]: 表明当前dump的为设置界面的帧信息,pid为31148
- Total frames rendered: 105 本次dump搜集了105帧的信息
- Janky frames: 2 (1.90%) 105帧中有2帧的耗时超过了16ms,卡顿概率为1.9%
- Number Missed Vsync: 0 垂直同步失败的帧
- Number High input latency: 0 处理input时间超时的帧数
- Number Slow UI thread: 2 因UI线程上的工作导致超时的帧数
- Number Slow bitmap uploads: 0 因bitmap的加载耗时的帧数
- Number Slow issue draw commands: 1 因绘制导致耗时的帧数
- HISTOGRAM: 5ms=78 6ms=16 7ms=4 … 直方图数据,表面耗时为0-5ms的帧数为78,耗时为5-6ms的帧数为16,同理类推。
HierarchView (已删除)
Hierarchy Viewer是随Android SDK发布的工具,位于Android SDK/tools/hierarchyviewer.bat,这个工具已经被删除很久了,而且测试时候需要root权限,不建议大家去深挖。这个工具最大的优点在于可以显示每一个视图Measure、layout、draw的时间,如果需要深度优化的时候可以用这个工具分析一下。
ps: HierarchView话说被AS的Layout Inspector工具所替代,事实上Layout Inspector只能用来检查层级和位置。
工具解读
在Hierarchy Viewer窗口中,你可以看到实际的运行的速度,所有的子View上面都有了3个圈圈。这三个圈圈依次代表:
- measure
- layout
- draw的
(取色范围为红、黄、绿色),如果你发现某个View上的圈是红色,那么说明这个View相对其他的View,该操作运行最慢,注意只是相对别的View,并不是说就一定很慢。
理论
优化过度绘制:
- 移除不必要的布局嵌套,减少深度。
- 使用merge替换include,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中.
- 使用ViewStub来替换visible=gone/visible的切换,ViewStub 标签最大的优点是当你需要时才会加载,使用它并不会影响UI初始化时的性能。要注意的是,使用时候如果两次初始化会引发一个异常,所以使用前先判断是否被加载过。
- 移除不必要的背景
- 在自定义view的onDraw中,如果涉及到重叠的绘制view时,可以考虑利用局部绘制避免过度绘制。
优化绘制时间:
这个问题的难度主要还是在定位上,由于HierarchView已经被删除,而GPU Rendering只能是起到一个指导的作用。这个就得用到另外一个工具systrace了,这个我将会放到下一个篇章来讲。
参考
- Android性能优化第(四)篇—Android渲染机制
- Android性能优化典范
- GPU呈现模式分析中颜色的意义
- [官方GPU呈现工具文档](