Android应用性能优化记录

虚拟机在执行GC垃圾回收操作时所有线程(包括UI线程)都需要暂停,当GC垃圾回收完成之后所有线程才能够继续执行(这个细节下面小节会有详细介绍)。也就是说当在16ms内进行渲染等操作时如果刚好遇上大量GC操作则会导致渲染时间明显不足,也就从而导致了丢帧卡顿问题

android的性能优化主要从下面4个方面入手

UI性能问题优化

UI卡顿常见原因
  1. 在UI线程中做轻微耗时操作,导致UI线程卡顿
  2. 布局太过复杂,layout嵌套太多,无法在16ms内完成渲染
  3. 同一时间执行动画次数过多,导致CPU、GPU负载过重
  4. View过度绘制,使某些像素在同一时间被多次绘制,导致CPU、GPU负载过重
  5. View频繁出发onMeasure、onLayout,致使累计耗时过长及整个View频繁的重新绘制
  6. 内存频繁出发GC过多(同一帧中频繁创建内存)导致暂时阻塞渲染操作
  7. 冗余资源导致加载和执行过慢

Layout层级分析

UI层级分析工具hierarchy view 分析layout视图情况,以及调试自定义view时及时观察measure layout draw的运行时间

GPU的过度绘制,在开发中选项中开启过渡绘制显示,可根据app界面的颜色显示查看是否过度绘制并优化

颜色 含义
无色 WebView等的渲染区域
蓝色 1x过度绘制
绿色 2x过度绘制
淡红色 3x过度绘制
红色 4x(+)过度绘制

譬如:优化布局层次机构,减少没必要的背景。暂时不显示的View 设置为gone而不是invisible

每个柱状图偏上都有一根代表16ms基准的绿色横线,每一条竖着的柱状线都包含三部分(蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间),只要我们每一帧的总时间低于基准线就不会发生UI卡顿问题(个别超出基准线其实也不算啥问题的)。

这个工具只能看出是否丢帧,却不能分析丢帧原因,所以需要配合traceview和systrace来进行原因追踪

Lint进行资源及冗余UI布局等优化

gc导致的性能分析(memory检测、gc日志输出、Allocation Tracker工具进行配合定位分析问题):

gc频繁执行的原因:

  1. 短时间内大量频繁的对象创建和释放操作(内存抖动现象)
  2. 短时间内已经存在大量的内存占用介于阈值边缘,当创建对象导致超越阈值时候触发gc操作

触发gc的主要原因:

  1. 内存分配失败
  2. 但分配的对象大小超过限定阈值(根据系统而定具体值)
  3. 对gc的显式调用(System.gc())
  4. 外部内存分配失败

从内存角度优化其中就是要避免gc的频繁执行造成UI视觉的卡顿:

  1. 检查代码是否有频繁触发的代码中存在大量内存分配
  2. 尽量避免在对此for循环中频繁分配内存
  3. 避免在自定义view中的ondraw方法中进行复杂的朝族及对象创建,比如paint的实例化不要写在ondraw中
  4. 对于并发下载应该避免多次创建线程对象,而应该用线程池的方式处理

分析完原因之后可配合Allocation Tracker工具进行gc导致的性能问题定位分析

Android studio Monitor面板的memory的tracview记录了应用程序每个函数执行的时间,以此来进行性能分析

ANR导致UI卡顿:

常见ANR的原因

  1. 按键触摸事件派发超时,一般阈值为5s
  2. 广播阻塞ANR,一般阈值为10s
  3. 服务超时ANR,一般阈值为20s
    发生ANR时可在/data/anr/traces.txt文件中查看分析原因

Memory内存性能优化

内存泄露性能分析

内存泄露产生原因:
在某些对象的生命周期执行完的时候,该对象本来应该被垃圾回收,但此时这个对象还被别的对象持有引用,那么就会导致内存泄露

代码举例分析:

1
2
3
4
5
6
7
8
9
10
11
12
public final class MainActivity extends Activity {
private DbManager mDbManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//DbManager是一个单例模式类,这样就持有了MainActivity引用,导致泄露
mDbManager = DbManager.getInstance(this);
}
}

上面例子中,在当前Acvitivy执行完onDestroy()后,这个Activity的应用被单例模式的DbManager对象持有,就无法得到垃圾回收,也就造成了内存泄露

内存泄露检测方式或工具:

查看方式 场景
AS的Memory窗口 平时用来直观了解自己应用的全局内存情况,大的泄露才能有感知。
DDMS-Heap内存监测工具 同上,大的泄露才能有感知。
dumpsys meminfo命令 常用方式,可以很直观的察觉一些泄露,但不全面且常规足够用。
leakcanary神器 比较强大,可以感知泄露且定位泄露;实质是MAT原理,只是更加自动化了,当现有代码量已经庞大成型,且无法很快察觉掌控全局代码时极力推荐;或者是偶现泄露的情况下极力推荐。

内存泄露导致的常见的问题:

  1. 应用卡顿,响应速度满(内存占用高,JVM会频繁触发GC)
  2. 后台进程被转化为空进程,从而被系统kill(内存超过阈值)
  3. 崩溃(超过内存阈值)

开发中应该如何规避内存泄露的几点建议:

  1. 不要对一个activity context保持长生命周期的引用
  2. 非静态内部类的静态实例容易造成内存泄露,尽量使用静态类和弱应用来处理
  3. 对象的注册和反注册没有成对出现,比如广播接收器、后台服务、观察者的注册和反注册
  4. 创建与关闭没有成对出现,比如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象用完后必须手动关闭
  5. 不要在执行频率很高的方法或循环中创建对象,可创建对象容易做缓存处理

内存溢出OOM性能分析

内存泄露和内存溢出的关系是,但内存泄露超过一定阈值后就会造成内存溢出

造成内存溢出的原因:

  1. 内存泄露,长时间累计无法释放导致oom
  2. 程序短时间内大量消耗内存(比如加载未处理的超大高清巨图)导致超过阈值,致使oom

从上面不难得出导致内存溢出的直接原因就是内存占用超过阈值

开发中规避内存溢出的建议:

  1. 不要直接加载高清大图,可用BitmapFactory.option设置图片属性,或者第三方图片框架加载,fresco glide
  2. 有些地方避免使用强引用,替换为弱应用
  3. 对需要批量处理的数据进行缓存处理,或者事务处理(数据多次加载,一次性提交,提升系统性能)
  4. 尽可能复用资源,比如字符串、颜色、尺寸、动画、布局等资源复用
  5. 尽量使用线程池代替多线程操作,节约内存和CPU占用
  6. 多用try catch进行异常处理,避免不必要的闪退
  7. 尽量优化代码逻辑,减少冗余,编译打包时进行优化对齐处理

API使用、代码编写性能分析

字符串的拼接 +、StringBuffer(线程安全)、StringBuilder的选择

HashMap、ArrayMap、SparseMap的使用选择

HashMap 内部使用默认容量的数组存储数据,每个元素存放一个链表头节点,其内部结构为链表结构,因为元素查询采用遍历法,所以在查询元素时效率低,成本高

SparseMap 内部避免了对key进行自动装箱,采用两个数组来存储数据,一个存储key,一个存储value,内部数据采用压缩方式,从而节约内存空间,元素查询采用二分法,所以性能比hashmap高,比hashmap省内存

ArrayMap内部使用两个数组进行数据存储,一个记录Key的Hash值,一个记录Value值,它和SparseArray类似,也会在查找时对Key采用二分法。

当数据量不大(千位级内)且Key为int类型时使用SparseArray替换HashMap效率高;当数据量不大(千位级内)且数据类型为Map类型时使用ArrayMap替换HashMap效率高;其他情况下HashMap效率相对高于二者

使用ContentProviderOperation避免多次调用ContentProvider

  1. 所有的操作都在一个事务中执行,可以保证数据的完整性。

  2. 批量操作在一个事务中执行,所以只用打开、关闭一个事务。

  3. 减轻应用程序与ContentProvider间的多次频繁交互,提升性能

其他代码编写性能优化的建议

  1. 避免使用枚举,编译后不但占空间,而且加载也费时
  2. Handler发送消息时尽量用obtain去获取已经存在的message进行复用,而不是每次使用都用创建新的message对象,减轻内存压力
  3. 尽量不要用getXXX、setXXX对机制的内部成员进行操作,直接使用,提高代码执行效率,也可避免65536问题
  4. 不要一味的为了设计模式去抽象代码,代码抽象系数和代码执行时间成正比

Android应用电池耗电性能分析

常见耗电量大的原因:

  1. 网络数据交互
  2. GPS定位
  3. 大量内存性能问题
  4. 冗余的后台线程和服务

针对上面的原因提出相应的对策:

  1. 网络操作之前进行网络状态判断
  2. 数据传输中使用高效的数据交互方式,比如json
  3. 对定位要求不高的的场景使用网络定位,而不是GPS定位
  4. 减少网络请求次数和请求时间间隔
  5. 后台任务要尽可能少的唤醒CPU