Android App开发过程中,很多的项目从0到1,再经历过N次版本迭代之后,性能问题基本上都会慢慢的上升,严重的则影响到了一款产品的核心运营数据,甚至更为糟糕,因此可见APP的性能的重要性。计划通过一些文章来记录一下Android App性能优化的一些常用问题,解决方案等,作为自己学习知识沉淀的同时也可以分享给有需要的同学。
对于用户来说,体验一款APP首先就是打开这个APP,那么APP的启动快慢就是给用户的第一感觉,启动慢的自然感觉不会太好。这好比是我们平常使用浏览器打开网页时,当一个页面长时间不能刷出来,大概率的会离开这个页面,貌似这就是互联网中的8秒定律。
最简单的理解就是App从来没有启动过,第一次点击启动。实际上经历的过程主要分为两大步,启动进程 + 启动界面。
主要步骤大致如下
温启动是进程还在,启动的时候不需要再创建进程,即省掉了两步较耗时的操作startProcess和bindAppliation,因为启动速度相对冷启动较快。
热启动差不多就是把应用从后台切到前台,不需要创建进程,只需要走一下Activity的onResume,重新绘制一下,所以启动速度是最快的。
其实App启动的过程有很多步,其实在startProcess, bindApplication,mesuare/layout/draw这些过程中,我们能干预的相对比较少,大部分是由Android系统完成,其实主要的就是bindApplication中对Application的初始化(这里指onCreate())和Activity的生命周期回调方法中干预,这些方法的回调都是由AMS完成,我们没有办法干预。
adb shell am start -W /
通过命令有较大的局限性,相对比较笼统,没有办法具体到某个方法,但也是一种不错的方法。
举例:
ThisTime: 最后一个Activity启动耗时
TotalTime:所有Activity的耗时
WaitTime:AMS启动Activity的总耗时
(WaitTime >= TotalTime >= ThisTime)
该种方式比较精确,是一种比较严谨的启动时间计算。实际上就是通过两个位置的
System.currentTimeMillis()
差值来计算启动时间的。
举例:通过打点测试APP启动时间,开始记录在Application的attachBaseContext()方法中,结束记录在Activity的layout完成(这里可以通过contentView.getViewTreeObserver().addOnPreDrawListener()来实现,即真正看到界面),注意这里不要放在onWindowFocusChanged()中作为结束打点,因为该方只是Activity的首帧时间。
对于工具的选择,我们应该在不同的场景下面选择不同的工具,发挥其最大作用,不应该只局限于某一种工具。
优势及特点:1、图形化的形式展示执行时间,调用堆栈等
2、信息全面,包含有所有线程
使用方式:这里只列举一种打点的方式,还有通过AS操作的,DDMS操作的,出来的界面都是一样。
//1、开始位置
Debug.startMethodTracing("traceName")
//2、结束位置
Debug.stopMethodTracing()
//3、生成的文件在sd卡:/sdcard/Android/data//files
打开后的Demo.trace文件是什么 样的呢?
0:选择线程中执行的各方法
1:self, 方法自身执行的时间
2:方法内部调用其它方法执行的时间
Total = 1 + 2(即self + children)
另外在3处有两种选择
Wall Clock Time: 线程执行的总时间
Thread Time: 该线程所分配到的CPU时间片(实际上会小于Wall Clock Time)
对于下面4个tab(一般常用Call Chart、Top Down),实际上相反的两组
Call Chart、Top Down
Flame Chart、Bottom Up
这里说明一下Call Chart的情况(主要是颜色代表情况、从上到下的调用顺序)
对于上面的traceView,其实是有一些自己的不足的地,主要表现在运行时开销严重,整体都变慢,从而可能误导我们的优化方向。而systrace则没有这样的问题,相对来说是比较轻量级的,它是通过结合Android的内核数据来生成HTML报告,这里注意在API 18以上推荐使用TraceCompat(向下兼容)。
systrace可以参考google官方文档
https://developer.android.google.cn/studio/profile/systrace?hl=en
使用方式,这里同样以打点为例,也可以通过adb命令。
然后准备python脚本命令
python /platform-tools/systrace/systrace.py -b 32768 -t 5 -a -o demo.html sched gfx view wm am app
然后再打开生成的html,这里不再展开说明怎么看systrace的html报告,工具具体怎么用,怎么看可上google文档,也可以看网上一些同学写的不错的博客介绍。
三、一种优雅的获取类中函数耗时方法(AOP)
对于大多数开发者来说测试一个函数的消耗时间都是通过两个System.currentMinions()相减,如果我们想看一个类里面每一个方法的耗时是多少,怎么办了?这里我们以Application类的启动为例。第一种当然就是为每个方法都添加上两个时间戳相减,写成之后就是这样的,如果我们添加新方法,那我们又得写这个LOG,而看上去也很不友好。
除了这种比较笨的方法之外,其实还有一种比较优雅的方式实现无侵入式的检测每个方法的耗时情况,就是通过AOP去实现,我们可以使用Android端的一个AOP框架来实现(AspectJ)。既然是无侵入,那应该就是这样的啦。
如何通过AspectJ实现?
1、接入AspectJ
//根目录gradle文件中
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
//app对就的gradle文件中
apply plugin: 'android-aspectjx'
implementation 'org.aspectj:aspectjrt:1.8.+'
2、创建AspectJ关联类
@Aspect
public class AspectJ {
private static final String TAG = "AspectJ";
@Around("call(* com.yifang.apm.DemoApplication.**(..))")
public void getTime(ProceedingJoinPoint joinPoint) {
long start = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.d(TAG, "spendTime: " + (System.currentTimeMillis() - start));
}
}
直接再次安装运行
当我们需要修改其它类的时候,只需要在注解中修改类名就可以,非常方便,最大的好处是0入侵。