性能分析与优化方案

性能问题分类

1、渲染问题:过度绘制、布局冗杂

2、内存问题:内存浪费(内存管理)、内存泄漏

3、功耗问题:耗电


性能优化工具


以下优化工具在下面文章中具体介绍使用方法。


1、手机开发者选项:调试GPU过度绘制、启用严格模式、显示CPU使用情况、GPU呈现模式分析、显示所有"应用程序无响应"。(小米手机开发开发者选项中名字)


2、IDE中:Android Studio,比如静态代码检测工具、Memory Monitor、CPU Monitor、NetWork Monitor、GPU Monitor、Layout Inspector、Analyze APK等。


3、SDK中:sdk\tools,比如DDMS、HierarchyViewer、TraceView等。


4、第三方工具:MAT、LeakCanary、GT等。


性能优化指标


1、渲染

  • 滑动流畅度:FPS,即Frame per Second,一秒内的刷新帧数,越接近60帧越好;

  • 过度绘制:单页面的3X(粉红色区域) Overdraw小于25%

  • 启动时间:这里主要说的是Activity界面启动时间,一般低于300ms,需要用高频摄像机计算时间。


2、内存

  • 内存大小:峰值越低越好,需要优化前后做对比

  • 内存泄漏:需要用工具检查对比优化前后


3、功耗

  • 单位时间内的掉电量,掉电量越少越好,业内没有固定标准。华为有专门测试功耗的机器,以及自己的标准。


1 渲染问题


先来看看造成应用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;


1、过度绘制


造成过度优化的关键是什么?多余的背景(Background)


参考:

http://jaeger.itscoder.com/android/2016/09/29/android-performance-overdraw.html


优化步骤如下:


1、移除或修改Window默认的Background

2、移除XML布局文件中非必需的Background

3、按需显示占位背景图片

4、控制绘制区域

2 布局优化


布局太过复杂,层级嵌套太深导致绘制操作耗时,且增加内存的消耗。
我们的目标就是,层级扁平化。


布局优化的建议:

第一个建议:可以使用相对布局减少层级的就使用相对布局,否则使用线性布局。Android中RelativeLayout和LinearLayout性能分析,参考:http://www.jianshu.com/p/8a7d059da746#

第二个建议:用merge标签来合并布局,这可以减少布局层次。

第三个建议:用include标签来重用布局,抽取通用的布局可以让布局的逻辑更清晰明了,但要避免include乱用。

第四个建议:避免创建不必要的布局层级。(最容易发生的!)

第五个建议:使用惰性控件ViewStub实现布局动态加载


如何借助工具查看代码布局?


Android SDK 工具箱中有一个叫做 Hierarchy Viewer 的工具,能够在程序运行时分析 Layout。
可以用这个工具找到 Layout 的性能瓶颈
该工具的使用条件:模拟器或者Root版真机。
如何开启该功能:AndroidStudio中,Tools — Android — Android Devices Monitor
该工具的缺点:使用起来麻烦。




ViewStub的使用


这个标签最大的优点是当你需要时才会加载,使用他并不会影响UI初始化时的性能。

使用ViewStub可以避免内存的浪费,加快渲染速度。


其实ViewStub就是一个宽高都为0的一个View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过android:layout属性来设置。



当准备inflate ViewStub时,调用inflate()方法即可。还可以设定ViewStub的Visibility为VISIBLE或INVISIBLE,也会触发inflate。注意的是,使用inflate()方法能返回布局文件的根View。



注意:使用ViewStub加载的布局中不能使用merge标签。

3 内存浪费


程序内存的管理是否合理高效对应用的性能有着很大的影响。
推荐阅读Android性能优化典范-第3季,参考:
http://hukai.me/android-performance-patterns-season-3/


ArrayMap


Android为移动操作系统特意编写了一些更加高效的容器,例如ArrayMap、SparseArray。

为了解决HashMap更占内存的弊端,Android提供了内存效率更高的ArrayMap。


先来看看HashMap的原理


HashMap的整体结构如下:



存储位置的确定流程:



再看来看看ArrayMap是如何优化内存的


它内部使用两个数组进行工作,其中一个数组记录key hash过后的顺序列表,另外一个数组按key的顺序记录Key-Value值,如下图所示:



当你想获取某个value的时候,ArrayMap会计算输入key转换过后的hash值,然后对hash数组使用二分查找法寻找到对应的index,然后我们可以通过这个index在另外一个数组中直接访问到需要的键值对。



既然ArrayMap中的内存占用是连续不间断的,那么它是如何处理插入与删除操作的呢?它跟HashMap有什么区别?二者之间的删除插入效率有什么差异?请看下图所示,演示了Array的特性:




HashMap与ArrayMap之间的内存占用效率对比图如下:



与HashMap相比,ArrayMap在循环遍历的时候更加高效。



什么时候使用ArrayMap呢?


1、对象个数的数量级最好是千以内,没有频繁的插入删除操作

2、数据组织形式包含Map结构


Autoboxing(避免自动装箱)


Autoboxing的行为还经常发生在类似HashMap这样的容器里面,对HashMap的增删改查操作都会发生了大量的autoboxing的行为。当key是int类型的时候,HashMap和ArrayMap都有Autoboxing行为。


SparseArray(项目中用到较多 -- 后面再说如何利用工具查找该用SparseArray而没有用到的地方)


为了避免Autoboxing行为Android提供了SparseArray,此容器使用于key为int类型。
SparseBooleanMap,SparseIntMap,SparseLongMap等容器,是key为int,value类型相应为boolean、int、long等。


Enum(枚举,项目中较多使用,应尽量避免)


Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
Android官方强烈建议不要在Android程序里面使用到enum。
关于enum的效率,请看下面的讨论。假设我们有这样一份代码,编译之后的dex大小是2556 bytes,在此基础之上,添加一些如下代码,这些代码使用普通static常量相关作为判断值:



增加上面那段代码之后,编译成dex的大小是2680 bytes,相比起之前的2556 bytes只增加124 bytes。假如换做使用enum,情况如下:



使用enum之后的dex大小是4188 bytes,相比起2556增加了1632 bytes,增长量是使用static int的13倍。不仅仅如此,使用enum,运行时还会产生额外的内存占用,如下图所示:



推荐一些文章:


  • HashMap,ArrayMap,SparseArray源码分析及性能对比,参考:http://www.jianshu.com/p/7b9a1b386265#

  • Android性能优化--小心自动装箱:

    http://blog.csdn.net/lgz_ei/article/details/69208784

  • Android性能优化篇:Android中如何避免创建不必要的对象:http://blog.csdn.net/jia635/article/details/52525243

  • HashMap、ArrayMap、SparseArray分析比较:http://blog.csdn.net/chen_lifeng/article/details/52057427

  • Android性能优化之String篇:

    http://www.androidchina.net/5940.html

  • SharedPreferences的commit和apply分析:http://blog.csdn.net/u010198148/article/details/51706483


4 内存泄露


什么是内存泄漏?


一些不用的对象被长期持有,导致内存无法被释放。


可能发生内存泄漏的地方有哪些?


内部类引用导致Activity的泄漏


在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。


最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。


为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。


Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏。


考虑使用Application Context而不是Activity Context。


例如:全局Dialog或者Context被单例持有。


静态造成的内存泄漏


还有静态变量持有View,例如:

private static View view;
void setStaticView() {
  view = findViewById(R.id.sv_button);
}


注意监听器的注销(稍后利用工具分析一个例子)


  • regist就要unregist

  • 注意Cursor对象是否及时关闭(项目中也存在,不再列举)

  • WebView的引起的泄漏(暂时没有研究)


使用工具分析定位解决内存泄漏


Memory monitor


MAT(Memory Analyzer Tool)


LeakCanary



LeakCanary 中文使用说明,参考:https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

LeakCanary:让内存泄露无所遁形,参考:https://www.liaohuqiu.net/cn/posts/leak-canary/


TraceView(不做详细分析)


GT(应该更适合测试同学测试APP性能)


利用GT,仅凭一部手机,无需连接电脑,您即可对APP进行快速的性能测试(CPU、内存、流量、电量、帧率/流畅度等等)、开发日志的查看、Crash日志查看、网络数据包的抓取、APP内部参数的调试、真机代码耗时统计等。


GT官网:http://gt.qq.com/index.html


内存使用策略优化


  • onLowMemory():Android系统提供了一些回调来通知当前应用的内存使用情况,通常来说,当所有的background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调。在这种情况下,需要尽快释放当前应用的非必须的内存资源,从而确保系统能够继续稳定运行。


  • onTrimMemory(int):Android系统从4.0开始还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递参数,代表不同的内存使用情况,收到onTrimMemory()回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用。


文章推荐:


  • Android内存优化之OOM:

    http://hukai.me/android-performance-oom/

  • 胡凯Android性能典范系列


Lint:静态代码分析工具

猜你喜欢

转载自blog.csdn.net/qq_32671919/article/details/80239299