一、安卓UI性能检测与优化
UI是安卓应用程序与用户打交道的最直接途径,UI设计的好不好,直接影响到用户的体验,如果没有达到他们心目中的自然流畅细节,用户要是能够感觉出来,少则影响心情,多则卸载应用;所以一个应用的UI显示性能问题就不得不被开发人员重视。
1.UI卡顿常见原因:
- 在UI线程中做了耗时操作,导致UI线程卡顿;
- 布局Layout过于复杂,无法在16ms内完成渲染;
- 同一时间动画执行的次数过多,导致CPU或GPU负载过重;
- View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重;
- View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;
- 内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;
- 冗余资源及逻辑等导致加载和执行缓慢;
2.UI性能分析
2.1使用HierarchyViewer分析UI性能
Android SDK提供了一个工具HierarchyViewer,可以用来分析UI布局复杂程度及冗余等
在\sdk\tools\目录下有个hierarchyviewer.bat,双击可启动(如下图),注意,启动前你需要启动你的模拟器或者接入你的手机进入调试模式
(1)选中其中一个item,点击Load View Hierarchy,会出现如下图:
上图显示了我的应用程序中MainActivity的View树,通过这个树可以分析出View嵌套的冗余层级,左下角可以输入View的id直接自动跳转到中间显示;Save as PNG用来把左侧树保存为一张图片;Capture Layers用来保存为psd的PhotoShop素材;右上角为View的总体框架图;右侧居中显示的是选中View的当前属性和状态;右下角显示当前View在Activity中的位置以及模块分布等;左下角三个进行切换;Load View Hierarchy用来手动刷新变化(在页面变更时要手动刷新)。
上图可以很方便的查看到当前View的许多信息;上图最底那三个彩色圆点代表了当前View的性能指标,从左到右依次代表测量、布局、绘制的渲染时间,红色和黄色的点代表速度渲染较慢的View(当然了,有些时候较慢不代表有问题,譬如ViewGroup子节点越多、结构越复杂,性能就越差)。
在自定义View的性能调试时,可以使用HierarchyViewer上面的invalidate Layout和requestLayout按钮,它可以帮助我们调试自定义View执行invalidate()和requestLayout()过程,我们只需要在代码的相关地方打上断点就行了,接下来通过它观察绘制即可。
2.2使用Android Lint进行资源和布局的优化
Android Lint是SDK Tools 16 (ADT 16)之后才引入的工具,通过它对Android工程源代码进行扫描和检查,可发现潜在的问题,以便程序员及早修正这个问题。也就是说,它可以用来检查你的代码是否合法,当然,它也可以用来检测UI布局和资源文件的冗余性。
(1)在Android Studio中使用Lint:
将鼠标放在代码区点击右键->Analyze->Inspect Code–>界面选择你要检测的模块->OK 出现如下图:
如上,它提示我们,控件的显示的字符串最好使用@String的形式,当前布局中有个RelativeLayout冗余了,应该去掉。
(2)在Eclipse中,则提供了一个全局的lint检查,可以在Window->Show View->other->Lint Warnings调出lint检查视图(一般都默认出现在控制台区域),Lint Warnings如下图
它也给了我们相应的提示和问题出现的位置,大家可以自行尝试。另外,Eclipse在编译导出APK时,也会执行lint检查,这时的lint检查将更加详细,你能看到更多的信息。
2.3 使用Traceview进行分析优化
关于UI卡顿问题我们还可以通过运行Traceview工具进行分析,它是一个分析器,记录了应用程序中每个函数的执行时间;我们可以打开DDMS然后选择一个进程,接着点击上面的“Start Method Profiling”按钮(红色小点变为黑色即开始运行),然后操作我们的卡顿UI(小范围测试,所以操作最好不要超过5s),完事再点一下刚才按的那个按钮,稍等片刻即可出现下图,如下:
整个界面上面是你测试的进程中每个线程运行的时间线,下面是每个方法执行的各个指标的值。通过上图的时间面板可以直观发现,整个trace时间段main线程做的事情特别多,其他的做的相对较少。当我们选择上面的一个线程后可以发现下面列出了该方法的Parents和Children,它主要展示了线程中各个方法的调用信息(CPU使用时间、调用次数等),这些信息就是我们分析UI性能卡顿的核心关注点,所以我们先看几个重要的属性说明,如下:
属性名 | 含义 |
name | 线程中调运的方法名; |
Incl CPU Time | 当前方法(包含内部调运的子方法)执行占用的CPU时间; |
Excl CPU Time | 当前方法(不包含内部调运的子方法)执行占用的CPU时间; |
Incl Real Time | 当前方法(包含内部调运的子方法)执行的真实时间,ms单位; |
Excl Real Time | 当前方法(不包含内部调运的子方法)执行的真实时间,ms单位; |
Calls+Recur Calls/Total | 当前方法被调运的次数及递归调运占总调运次数百分比; |
CPU Time/Call | 当前方法调运CPU时间与调运次数比,即当前方法平均执行CPU耗时时间; |
Real Time/Call | 当前方法调运真实时间与调运次数比,即当前方法平均执行真实耗时时间;(重点关注) |
有了对上面Traceview图表的一个认识之后我们就来看看具体导致UI性能后该如何切入分析,一般Traceview可以定位两类性能问题:
- 方法调运一次需要耗费很长时间导致卡顿;
- 方法调运一次耗时不长,但被频繁调运导致累计时长卡顿。
譬如有时候我们写完App在使用时不觉得有啥大的影响,但是当我们启动完App后静止在那却十分费电或者导致设备发热,这种情况我们就可以打开Traceview然后按照Cpu Time/Call或者Real Time/Call进行降序排列,然后打开可疑的方法及其child进行分析查看,然后再回到代码定位检查逻辑优化即可。
2.4
使用Systrace进行分析优化
Systrace它是对整个系统进行分析(同一时间轴包含应用及SurfaceFlinger、WindowManagerService等模块、服务运行信息),不过这个工具需要你的设备内核支持trace(命令行检查:/sys/kernel/debug/tracing)且设备是eng或userdebug版本才可以,所以使用前麻烦自己确认一下。
打开DDMS->点击上面工具栏的Capture system wide trace using Android systrace->设置时间与选项点击OK就开始了抓取,接着操作APP,完事生成一个trace.html文件,用Chrome打开即可如下图:
在浏览器中浏览分析该文件我们可以通过键盘的W-A-S-D键来放大缩小平移视图,由于上面我们在进行trace时选择了一些选项,所以上图生成了左上方相关的CPU频率、负载、状态等信息,其中的CPU N代表了CPU核数,每个CPU行的柱状图表代表了当前时间段当前核上的运行信息;下面我们再来看看SurfaceFlinger的解释,如下:
可以看见上面左边栏的SurfaceFlinger其实就是负责绘制Android程序UI的服务,所以SurfaceFlinger能反应出整体绘制情况,可以关注上图VSYNC-app一行可以发现前5s多基本都能够达到16ms刷新间隔,5s多开始到7s多大于了15ms,说明此时存在绘制丢帧卡顿;同时可以发现surfaceflinger一行明显存在类似不规律间隔,这是因为有的地方是不需要重新渲染UI,所以有大范围不规律,有的是因为阻塞导致不规律,明显可以发现0到4s间大多是不需要渲染,而5s以后大多是阻塞导致;对应这个时间点我们放大可以看到每个部分所使用的时间和正在执行的任务,具体如下:
可以发现具体的执行明显存在超时性能卡顿(原点不是绿色的基本都代表存在一定问题,下面和右侧都会提示你选择的帧相关详细信息或者alert信息),但是遗憾的是通过Systrace只能大体上发现是否存在性能问题,具体问题还需要通过Traceview或者代码中嵌入Trace工具类等去继续详细分析。
2.5 使用traces.txt文件分析ANR
ANR(Application Not Responding)是Android中应用响应超时的表现;ANR是直接卡死UI不动且必须要解掉的Bug,我们必须尽量在开发时避免它的出现。
我们应用开发中常见的ANR主要有如下几类:
- 设置中开启ANR弹窗,默认有事件派发才会触发弹框ANR);
- 广播阻塞ANR,一般阈值为10s(设置中开启ANR弹窗,默认不弹框,只有log提示);
- 服务超时ANR,一般阈值为20s(设置中开启ANR弹窗,默认不弹框,只有log提示);
当ANR发生时除了logcat可以看见的log以外我们还可以在系统指定目录下找到traces文件或dropbox文件进行分析,
然后我们用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)
......
//省略一些不常关注堆栈打印
......
2.6 UI性能优化总结
- 布局优化;尽量使用include、merge、ViewStub标签,尽量不存在冗余嵌套及过于复杂布局,尽量使用GONE替换INVISIBLE,使用weight后尽量将width和heigh设置为0dp减少运算,Item存在非常复杂的嵌套时考虑使用自定义View来取代,减少measure与layout次数等。
- 列表及Adapter优化;尽量复用getView方法中的相关View,不重复获取实例导致卡顿,列表尽量在滑动过程中不进行UI元素刷新等。
- 背景和图片等内存分配优化;尽量减少不必要的背景设置,图片尽量压缩处理显示,尽量避免频繁内存抖动等问题出现。
- 自定义View等绘图与布局优化;尽量避免在draw、measure、layout中做过于耗时及耗内存操作,尤其是draw方法中,尽量减少draw、measure、layout等执行次数。
- 避免ANR,不要在UI线程中做耗时操作,遵守ANR规避守则,譬如多次操作数据库操作等。
二、内存性能检测优化
每个Android应用程序都执行在自己的虚拟机中,那了解Java的一定明白,每个虚拟机必定会有堆内存阈值限制(值得一提的是这个阈值一般都由厂商依据硬件配置及设备特性自己设定,没有统一标准,可以为64M,也可以为128M等;它的配置是在Android的属性系统的/system/build.prop中配置dalvik.vm.heapsize=128m即可,若存在dalvik.vm.heapstartsize则表示初始申请大小),也即一个应用进程同时存在的对象必须小于阈值规定的内存大小才可以正常运行。
接着我们运行的App在自己的虚拟机中内存管理基本就是遵循Java的内存管理机制了,系统在特定的情况下主动进行垃圾回收。但是要注意的一点就是在Android系统中执行垃圾回收(GC)操作时所有线程(包含UI线程)都必须暂停,等垃圾回收操作完成之后其他线程才能继续运行。这些GC垃圾回收一般都会有明显的log打印出回收类型,常见的如下:
- GC_MALLOC——内存分配失败时触发;
- GC_CONCURRENT——当分配的对象大小超过一个限定值(不同系统)时触发;
- GC_EXPLICIT——对垃圾收集的显式调用(System.gc()) ;
- GC_EXTERNAL_ALLOC——外部内存分配失败时触发;
1.
Android内存泄露检测
众所周知,在Java中有些对象的生命周期是有限的,当它们完成了特定的逻辑后将会被垃圾回收;但是,如果在对象的生命周期本来该被垃圾回收时这个对象还被别的对象所持有引用,那就会导致内存泄漏;这样的后果就是随着我们的应用被长时间使用,他所占用的内存越来越大。
(1)AndroidStudio中有个Memory窗口如下(在Debug模式下查看内存波动),这里就不详细说明了:
(2)DDMS-Heap内存监测工具,窗口如下
选择工具栏中的update heap既可以在Heap视图中看到相关内存分配和GC信息
(3)使用MAT内存检测工具
一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具
这个工具是非常专业级的工具,很多大神使用,但它过于庞大和复杂,这里不再赘述,有兴趣的可以自行搜索
(4)使用LeakCanary内存泄漏检测工具