一款APP对于用户来说,是最先接触和感受到的,如果我们的UI交互和动画做出来用起来不是那么流畅自然,用户是能够感受到的,轻则影响使用欲望,重则卸载应用。所以对于UI性能的优化应该引起我们足够的重视。
1,UI卡顿的原理
人类大脑与眼睛对于画面的连贯性感知是有一个界限的,比如我们看电影会觉得画面连贯的帧率为24fps,对于经常接触竞技游戏的同学应该也清楚60fps是保证游戏正常不卡顿的一个界限。对于andorid手机也是一样对于屏幕操作的连贯性,andorid平台也把这流畅的频率设定为60fps。 SO:
我们在开发app的时候对于UI性能频率目标要保持在60fps,即:
60帧/秒-----------16ms/帧;就是尽量保证每次在16ms内完成完成cpu gpu计算、绘制、渲染等操作。
针对andorid系统的设计还有一个比较重要的常识,它有一个GC垃圾回收机制,虚拟机在执行GC垃圾回收时包括UI线程在内的所有线程都要暂停,当GC垃圾回收完毕之后所有其他线程才能够继续执行。如果在进行绘制渲染的16ms内时刚好碰上了大量的GC操作就会导致绘制渲染的时间不足,从而导致丢帧卡顿的问题。
2,UI卡顿常见原因
1,人为在UI线程做微耗时操作,导致UI卡顿
2,布局layout过于复杂嵌套层级过多,无法再16ms内完成绘制渲染。
3,同一时间执行了过多的动画,
导致cpu或GPU短时间负载过重。
4,view被过度绘制,导致某些像素在同一帧时间被绘制多次,导致UI卡顿。
5,view频繁的触发measure、layout,导致measeure、layout累计耗时过多以及整个view被频繁的绘制。
6,内存频繁触发GC,导致短时间的阻塞渲染操作
7,冗余资源和逻辑等导致加载和执行缓慢 还有 ANR!!!
3,UI卡顿分析解决方法
1,使用HierarchyViewer分析性能
我们可以通过SDK提供的工具HierarchyViewer来进行UI布局复杂程度以及冗余等分析,启动方式:
在android的工具文件夹里找到:\andorid\tools\hierarchyViewer.bat启动window界面,
在AndroidStudio中的打开方式为
Tools > Android > Android Device Monitor
> openPerspective>
Hierarchy Viewer。
![](https://i-blog.csdnimg.cn/blog_migrate/dca4cf1a27de34dde6029c24f8911d55.png)
一个Activity的View树,通过这个树可以分析出View嵌套的冗余层级,左下角可以输入View的id直接自动跳转到中间显示;Save as PNG用来把左侧树保存为一张图片;Capture Layers用来保存psd的PhotoShop分层素材;右侧剧中显示选中View的当前属性状态;右下角显示当前View在Activity中的位置等;左下角三个进行切换;Load View Hierarchy用来手动刷新变化(不会自动刷新的)。当我们选择一个View后会如下图所示:
由于过度绘制指在屏幕的一个像素上绘制多次(譬如一个设置了背景色的TextView就会被绘制两次,一次背景一次文本;这里需要强调的是Activity设置的Theme主题的背景不被算在过度绘制层级中),所以最理想的就是绘制一次,也就是蓝色(当然这在很多绚丽的界面是不现实的,所以大家有个度即可,我们的开发性能优化标准要求最极端界面下红色区域不能长期持续超过屏幕三分之一,可见还是比较宽松的规定),因此我们需要依据此颜色分布进行代码优化,譬如优化布局层级、减少没必要的背景、暂时不显示的View设置为GONE而不是INVISIBLE、自定义View的onDraw方法设置canvas.clipRect()指定绘制区域或通过canvas.quickreject()减少绘制区域等。
![](https://i-blog.csdnimg.cn/blog_migrate/cd7df4bba41f16ee5c7dc5909a09bc70.png)
类似上图可以很方便的查看到当前View的许多信息;上图最底那三个彩色原点代表了当前View的性能指标,从左到右依次代表测量、布局、绘制的渲染时间,红色和黄色的点代表速度渲染较慢的View(当然了,有些时候较慢不代表有问题,譬如ViewGroup子节点越多、结构越复杂,性能就越差)。
当然了,在自定义View的性能调试时,HierarchyViewer上面的invalidate Layout和requestLayout按钮的功能更加强大,它可以帮助我们debug自定义View执行invalidate()和requestLayout()过程,我们只需要在代码的相关地方打上断点就行了,接下来通过它观察绘制即可。
可以发现,有了HierarchyViewer调试工具,我们的UI性能分析变得十分容易,这个工具也是我们开发中调试UI的利器,在平时写代码时会时常伴随我们左右。
2,使用GPU过度绘制分析UI性能
我们对于UI性能的优化还可以通过开发者选项中的GPU过度绘制工具来进行分析。在设置->开发者选项->调试GPU过度绘制(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对settings当前界面过度绘制进行分析):
![](https://i-blog.csdnimg.cn/blog_migrate/fe4193145ad402cac69d4b603bbaf1c4.png)
开启以后我们可以看到各种颜色的区域,具体含义如下:
无色 | WebView等的渲染区域 |
蓝色 | 1x过度绘制 |
绿色 | 2x过度绘制 |
淡红色 | 3x过度绘制 |
红色 | 4x(+)过度绘制 |
3,使用GPU呈现模式图以及FPS考核UI性能
通过开发者选项中GPU呈现模式图工具来进行流畅度考量的流程是(注意:如果是在开启应用后才开启此功能,记得先把应用结束后重新启动)在设置->开发者选项->GPU呈现模式(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对settings当前界面上下滑动列表后的图表):
![](https://i-blog.csdnimg.cn/blog_migrate/ee413d01bee374add1464695684f5f87.png)
打开上图可视化工具后,我们可以在手机画面上看到丰富的GPU绘制图形信息,分别展示了StatusBar、NavgationBar、Activity区域等的GPU渲染时间信息,随着界面的刷新,界面上会以实时柱状图来显示每帧的渲染时间,柱状图越高表示渲染时间越长,每个柱状图偏上都有一根代表16ms基准的绿色横线,每一条竖着的柱状线都包含三部分(蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间),只要我们每一帧的总时间低于基准线就不会发生UI卡顿问题(个别超出基准线其实也不算啥问题的)。
可以发现,这个工具是有局限性的,他虽然能够看出来有帧耗时超过基准线导致了丢帧卡顿,但却分析不到造成丢帧的具体原因。所以说为了配合解决分析UI丢帧卡顿问题我们还需要借助traceview和systrace来进行原因追踪,下面我们会介绍这两种工具的。
4,使用lint进行资源以及冗余UI布局优化
在Android Studio 中使用Lint最简单的办法就是将鼠标放在代码区点击右键->Analyze->Inspect Code–>界面选择你要检测的模块->点击确认开始检测,等待一下后会发现如下结果:
![](https://i-blog.csdnimg.cn/blog_migrate/9da93c5dd2a2891f62429bd2eb4daecf.png)
5,使用traces.txt文件进行ANR分析
ANR(Application Not Responding)是Android中AMS与WMS监测应用响应超时的表现;之所以把臭名昭著的ANR单独作为UI性能卡顿的分析来说明是因为ANR是直接卡死UI不动且必须要解掉的Bug,我们必须尽量在开发时避免他的出现,当然了,万一出现了那就用下面介绍的方法来分析吧。
应用中常见的ANR主要有如下几类:
按键触摸事件派发超市ANR,一般阈值为5s
(设置中开启ANR弹窗,默认有事件派发才会触发弹框ANR);
广播阻塞ANR,一般阈值为10s(设置中开启ANR弹窗,默认不弹框,只有log提示);
服务超时ANR,一般阈值为20s(设置中开启ANR弹窗,默认不弹框,只有log提示);
然后我们用txt编辑器打开可以发现如下结构分析:
//显示进程id、ANR发生时间点、ANR发生进程包名 ----- pid 19073 at 2015-10-08 17:24:38 ----- Cmd line: com.example.yanbo.myapplication //一些GC等object信息,通常可以忽略 ...... //ANR方法堆栈打印信息!重点! DALVIK THREADS (18): "main" prio=5 tid=1 Sleeping | group="main" sCount=1 dsCount=0 obj=0x7497dfb8 self=0x7f9d09a000 | sysTid=19073 nice=0 cgrp=default sched=0/0 handle=0x7fa106c0a8 | state=S schedstat=( 125271779 68162762 280 ) utm=11 stm=1 core=0 HZ=100 | stack=0x7fe90d3000-0x7fe90d5000 stackSize=8MB | held mutexes= at java.lang.Thread.sleep!(Native method) - sleeping on <0x0a2ae345> (a java.lang.Object) at java.lang.Thread.sleep(Thread.java:1031) - locked <0x0a2ae345> (a java.lang.Object) //真正导致ANR的问题点,可以发现是onClick中有sleep导致。我们平时可以类比分析即可,这里不详细说明。 at java.lang.Thread.sleep(Thread.java:985) at com.example.yanbo.myapplication.MainActivity$1.onClick(MainActivity.java:21) at android.view.View.performClick(View.java:4908) at android.view.View$PerformClick.run(View.java:20389) at android.os.Handler.handleCallback(Handler.java:815) at android.os.Handler.dispatchMessage(Handler.java:104) at android.os.Looper.loop(Looper.java:194) at android.app.ActivityThread.main(ActivityThread.java:5743) at java.lang.reflect.Method.invoke!(Native method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:988) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783) ......
6,UI性能分析解决总结
通过上面UI性能的原理、原因、工具分析总结可以发现,我们在开发应用时一定要时刻重视性能问题,如若真的没留意出现了性能问题,不妨使用上面的一些案例方式进行分析。但是那终归是补救措施,在我们知道上面UI卡顿原理之后我们应该尽量从项目代码架构搭建及编写时就避免一些UI性能问题,具体项目中常见的注意事项如下:
1,布局优化:尽量使用include、merge、viewstub标签,尽量不存在冗余嵌套以及过于复杂的布局,尽量使用GONE替换invisible,用weight后尽量将witdth和height设成0dp,以减少运算,item存在非常复杂的嵌套时考虑使用自定义item view来取代,减少measeure与layout次数
2,列表及adapter优化,尽量复用getview方法中的相关view,不重复获取实例导致卡顿,列表尽量在滑动过程中不进行UI元素刷新。
3,背景和图片等内存优化,尽量减少不必要的背景设置背景设置,图片尽量压缩处理显示,避免频繁的内存抖动问题
4,自定义view绘图与布局,尽量避免在draw measeure layout中做耗时和内存操作,尤其是draw房中,尽量减少draw、measeure、layout等执行次数。
5,避免ANR,不要在UI线程中做耗时操作,
遵守ANR规避守则,譬如多次数据库操作等。