腾讯优测优分享 | Android应用性能优化个人总结–图形优化
Posted 腾讯优测
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了腾讯优测优分享 | Android应用性能优化个人总结–图形优化相关的知识,希望对你有一定的参考价值。
腾讯优测是专业的移动云测试平台,涵盖自动化测试-全面兼容性测试,远程真机租用,漏洞分析等,旗下优分享不定时提供大量的移动研发及测试相关的干货~
应用UI卡顿常见原因主要在以下几个方面:
1.人为在UI线程中做轻微耗时操作,导致UI线程卡顿;
2.布局Layout过于复杂,无法在16ms内完成渲染;
3.同一时间动画执行的次数过多,导致CPU或GPU负载过重;
4.View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重;
5.View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;
6.内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;
7.冗余资源及逻辑等导致加载和执行缓慢;
8.臭名昭著的ANR;
一、图形优化
渲染机制
大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。从设计师的角度,他们希望App能够有更多的动画,图片等时尚元素来实现流畅的用户体验。但是android系统很有可能无法及时完成那些复杂的界面渲染操作。
渲染操作通常依赖于两个核心组件:CPU与GPU。
CPU负责包括Measure,Layout,Record,Execute的计算操作,
GPU 负责Rasterization(栅格化)、渲染等操作
Android系统每隔16ms发出VSYNC信号,触发GPU对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成。
如果你的某个操作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。(卡顿现象)
用户容易在UI执行动画或者滑动ListView的时候感知到卡顿不流畅,是因为这里的操作相对复杂,容易发生丢帧的现象,从而感觉卡顿。有很多原因可以导致丢帧,也许是因为你的layout太过复杂,无法在16ms内完成渲染,有可能是因为你的UI上有层叠太多的绘制单元,还有可能是因为动画执行的次数过多。这些都会导致CPU或者GPU负载过重。
Why 60fps帧/秒? 为什么是16ms毫秒?
12fps大概类似手动快速翻动书籍的帧率,这明显是可以感知到不够顺滑的。
24fps使得人眼感知的是连续线性的运动,电影胶圈通常使用的帧率,
低于30fps是无法顺畅表现绚丽的画面内容的,60fps来达到想要的效果,
超过60fps是没有必要的。
开发app的性能目标就是保持60fps,这意味着每一帧你只有16ms=1000/60的时间。
VSYNC机制
通过Vsync信号来同步UI绘制和动画,使得它们可以获得一个达到60fps的固定的帧率;
为了理解App是如何进行渲染的,我们必须了解手机硬件是如何工作,那么就必须理解什么是VSYNC。
在讲解VSYNC之前,我们需要了解两个相关的概念:
Refresh Rate:代表了屏幕在一秒内刷新屏幕的次数,这取决于硬件的固定参数,例如60Hz。
Frame Rate:代表了GPU在一秒内绘制操作的帧数,例如30fps,60fps。
GPU会获取图形数据进行渲染,然后显示器硬件负责把渲染后的内容呈现到屏幕上,他们两者不停的进行协作。
不幸的是,刷新频率和帧率并不是总能够保持相同的节奏。如果发生帧率与刷新频率不一致的情况,就会容易出现Tearing的现象(画面上下两部分显示内容发生断裂现象,来自不同的两帧数据发生重叠)。
情况一:GPU Frame rate > LED Refresh rate(不常见)
在这种情况下,某些帧显示的画面内容就会与上一帧的画面相同。糟糕的事情是,帧率从超过60fps突然掉到60fps以下,这样就会发生LAG,JANK,HITCHING等卡顿掉帧的不顺滑的情况。这也是用户感受不好的原因所在。
垂直同步及二缓冲以及三缓冲
平滑的动画一般需要每秒六十帧。而帧是由像素点构成的。当屏幕绘制一帧的时候,像素点是一行一行的进行填充的。
显示屏(LCD,AMOLED或者别的什么)从图形芯片(GPU)获取每帧的数据,然后一行一行的进行绘制。理想状况下,你期望显示屏在绘制完一帧之后,图形芯片整好能提供新帧的数据。
图像撕裂的状况就发生在屏幕绘制图像到一半的时候,GPU就载入了新一帧的数据,以致你最终得到的数据帧是半个帧的新数据和半个帧的老数据。
垂直同步,用来同步的。它告知GPU在载入新帧之前,要等待屏幕绘制完成前一帧。
除了垂直同步,还有android双缓冲机制。
缓冲就是帧构建和保存的容器。
android的双缓冲机制,它可以在显示一帧的同时进行另一帧的处理。在图中,就是缓冲A和B。当显示缓冲A时,系统在缓冲B中构建新的帧。完成后,则交换缓冲。显示缓冲B,而A则被清空,继续下一帧的绘制。
当某帧的绘制时间超过16毫秒时,双缓冲的问题就暴露出来了。
我们知道,仅在收到垂直同步脉冲时,才会进行帧(缓冲)切换。之间的空白表示浪费的时间。这就是ICS的缺陷。
三缓冲机制
B. 三缓冲支持,改善GPU和CPU之间绘制节奏不一致的问题;
一般情况下,它只使用了双缓冲,但当需要的时候,会用三倍缓冲来进行增强。这样,即将输入延迟降低到最少,又能在发生意外的情况下保持画面流畅。
过度重绘Overdraw
Overdraw描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。
当设计上追求更华丽的视觉效果的时候,我们就容易陷入采用越来越多的层叠组件来实现这种视觉效果的怪圈。这很容易导致大量的性能问题,为了获得最佳的性能,我们必须尽量减少Overdraw的情况发生。
幸运的是,我们可以通过手机设置里面的开发者选项,打开Show GPU Overdraw的选项,可以观察UI上的Overdraw情况。
蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。
Overdraw有时候是因为你的UI布局存在大量重叠的部分,还有的时候是因为非必须的重叠背景。例如某个Activity有一个背景,然后里面的Layout又有自己的背景,同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色Overdraw区域,增加蓝色区域的占比。这一措施能够显著提升程序性能。
1、抽象布局标签
(1) <include>标签
include标签常用于将布局中的公共部分提取出来供其他layout共用,以实现布局模块化,这在布局编写方便提供了大大的便利。
下面以在一个布局main.xml中用include引入另一个布局foot.xml为例。main.mxl代码如下:
Java
其中include引入的foot.xml为公用的页面底部,代码如下:
Java
<include>标签唯一需要的属性是layout属性,指定需要包含的布局文件。可以定义android:id和android:layout_*属性来覆盖被引入布局根节点的对应属性值。注意重新定义android:id后,子布局的顶结点i就变化了。
(2) <viewstub>标签
Java
(3) <merge>标签
在使用了include后可能导致布局嵌套过多,多余不必要的layout节点,从而导致解析变慢,不必要的节点和嵌套可通过hierarchy viewer(下面布局调优工具中有具体介绍)或设置->开发者选项->显示布局边界查看。
merge标签可用于两种典型情况:
a.布局顶结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容试图的parent view就是个FrameLayout,所以可以用merge消除只剩一个。
b.某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。
以(1) <include>标签的示例为例,用hierarchy viewer查看main.xml布局如下图:
可以发现多了一层没必要的RelativeLayout,将foot.xml中RelativeLayout改为merge,如下:
Java
运行后再次用hierarchy viewer查看main.xml布局如下图:
这样就不会有多余的RelativeLayout节点了。
2、去除不必要的嵌套和View节点
(1) 首次不需要使用的节点设置为GONE或使用viewstub
(2) 使用RelativeLayout代替LinearLayout
大约在Android4.0之前,新建工程的默认main.xml中顶节点是LinearLayout,而在之后已经改为RelativeLayout,因为RelativeLayout性能更优,且可以简单实现LinearLayout嵌套才能实现的布局。
4.0及以上Android版本可通过设置->开发者选项->显示布局边界打开页面布局显示,看看是否有不必要的节点和嵌套。4.0以下版本可通过hierarchy viewer查看。
3、减少不必要的infalte
(1) 对于inflate的布局可以直接缓存,用全部变量代替局部变量,避免下次需再次inflate
如上面ViewStub示例中的
Java
(2) ListView提供了item缓存,adapter getView的标准写法,如下:
Java
4、其他点
(1) 用SurfaceView或TextureView代替普通View
SurfaceView或TextureView可以通过将绘图操作移动到另一个单独线程上提高性能。
普通View的绘制过程都是在主线程(UI线程)中完成,如果某些绘图操作影响性能就不好优化了,这时我们可以考虑使用SurfaceView和TextureView,他们的绘图操作发生在UI线程之外的另一个线程上。
因为SurfaceView在常规视图系统之外,所以无法像常规视图一样移动、缩放或旋转一个SurfaceView。TextureView是Android4.0引入的,除了与SurfaceView一样在单独线程绘制外,还可以像常规视图一样被改变。
(2) 使用Renderjavascript
RenderScript是Adnroid3.0引进的用来在Android上写高性能代码的一种语言,语法给予C语言的C99标准,他的结构是独立的,所以不需要为不同的CPU或者GPU定制代码代码。
(3) 使用OpenGL绘图
Android支持使用OpenGL API的高性能绘图,这是Android可用的最高级的绘图机制,在游戏类对性能要求较高的应用中得到广泛使用。
Android 4.3最大的改变,就是支持OpenGL ES 3.0。相比2.0,3.0有更多的缓冲区对象、增加了新的着色语言、增加多纹理支持等等,将为Android游戏带来更出色的视觉体验。
(4) 尽量为所有分辨率创建资源
减少不必要的硬件缩放,这会降低UI的绘制速度,可借助Android asset studio
5、布局调优工具
(1) hierarchy viewer
hierarchy viewer可以方便的查看Activity的布局,各个View的属性、measure、layout、draw的时间,如果耗时较多会用红色标记,否则显示绿色。
hierarchy viewer.bat位于<sdk>/tools/目录下。
示例图如下:
(2) layoutopt(Lint)
layoutopt是一个可以提供layout及其层级优化提示的命令行,在sdk16以后已经被lint取代,在Windows->Show View->Other->Android->Lint Warnings查看lint优化提示,lint具体介绍可见Improving Your Code with lint。
Android是如何利用GPU进行画面渲染???
XML
↓
View
每一个View都抽象为一个Render Node
每一个Render Node都关联有一个Display List Renderer,这个Display List不是Open GL里面的Display List。
Display List是一个绘制命令缓冲区。它也是一个树状结构
当View的成员函数onDraw被调用时,我们调用通过参数传递进来的Canvas的drawXXX成员函数绘制图形时,我们实际上只是将对应的绘制命令以及参数保存在一个Display List中
腾讯优测优分享-Android平台的碎片化问题解决