一、什么是启动时长?
1、启动时长一般包括三种场景,分别是:新装包的首次启动时长,冷启动时长、热启动时长
冷启动 和 热启动 :
(1)冷启动:当启动应用时,后台没有该程序的进程,此时启动的话系统会分配一个新的进程给应用。
(2)热启动:程序的进程依然存在,启动时通过已有进程启动进入到Activity显示页面的,就是热启动,或者从Android官网来看我们获取到的其实是温启动时长,就是Activity不存在的情况。
(3)新装包的启动时长:
新装包的启动时长,预估是最长的,并且在5.0以下及5.0以上的Android系统上的表现不同,因为:Android 5.0和更高版本使用名为ART的运行时,它原生支持从APK文件加载多个DEX文件。在应用安装时,它会执行预编译,扫描classes(..N).dex文件然后将其编译成单个.oat文件用于执行。而Android5.0以下的系统不支持,只能在程序点击启动之后,进行多个dex文件的加载,如果是在Android5.0以下的机型上获取时长,就能明显看到新装包启动时长要比其他启动时长都要长。
其中冷启动的过程如下图所示:
系统开始启动Activity的时间,就是从start u0这个时间看的,准备启动这个Activity的时间,就是这条日志前面打印出来的这个时间:
12-14 16:45:51.483 I/ActivityManager( 1370): START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ganji.android/.control.LaunchActivity bnds=[571,684][763,876] (has extras)} from uid 10021 on display 0
之后该Activity的第一帧展示出来的时候,会打印出Display的日志信息,如:
12-14 16:45:52.023 I/ActivityManager( 1370): Displayed com.ganji.android/.control.LaunchActivity: +521ms
从start u0到display第一帧的这个时间 包括了从启动进程到第一次布局与绘制的所有时间。这基本上是你需要知道的主要时间。它不包含用户点击app图标然后系统开始准备启动activity的时间,这是ok的
2、关于我们想要获取的三种场景的启动时长
(1)三种场景是启动的三种典型场景,这三种场景并没有包含将缓存和数据清除掉的情况;其中用户经历次数最多的是 “冷启动时长” 和“ 热启动时长”
(2)新装包的首次启动时长,一般情况下是最多的,在Android5.0以下系统上从APK文件加载多个dex文件的过程也包含在内。
冷启动时长居第二位,因为杀进程之后内存中的缓存会丢失,很多数据需要重新请求,并且需要重新启动进程;
热启动时长是最短的。
(3)我们想要获取启动时长的场景:
都是从初始的LaunchActivity页面进入到下一个的可操作的页面的时间;
比如以赶集网为例,新装包首次启动之后会跳转到选择城市的页面,我们需要获取的时长就是从 LaunchActivity的页面进入到SelectcityActivity的页面
因为用户一般会用到的场景是:选择城市进入到主页面,下次再启动都是直接进入到主页面,所以编写了uiautomator的脚本,能够通过UItest的方式选择城市;之后再分别获取 “冷启动时长” 和 “热启动时长”,即杀进程和正常退出程序的启动时长
二、如何获取?
1、因为通过logcat打印日志过滤ActivityManager的方式来获取启动时间,能够对其他APP也适用,因此最终确定用这种方式。
其中以赶集生活为例,新装包首次启动时长的获取,需要获取的是:从LaunchActivity的启动,到Displayed出来SelectCityActivity,刚开始用的是Displayed的后面的这个time的时长,但是跟开发确认之后,他说这个时间比用户的实际感知过程的时间还少一些,最终确定的是:
获取从start u0 LaunchActivity的时间到Display可操作的Activity的时间,拿到前面的时间节点之后,然后进行计算
以赶集网为例,三种场景下在 小米 4 + Android4.4.4的机型下,通过logcat抓到的日志如下:
新装包首次启动从Launch界面进入到选择城市的界面:
12-12 17:08:51.101 I/ActivityManager( 1191): START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.ganji.android/.control.LaunchActivity} from pid 5656
12-12 17:08:58.141 I/ActivityManager( 1191): Displayed com.ganji.android/.control.LaunchActivity: +6s803ms
12-12 17:08:59.041 I/ActivityManager( 1191): START u0 {flg=0x200000 cmp=com.ganji.android/.control.BetterCityActivity (has extras)} from pid 5668
12-12 17:08:59.711 I/ActivityManager( 1191): Displayed com.ganji.android/.control.BetterCityActivity: +654ms
热启动时长:
12-12 17:11:23.571 I/ActivityManager( 1191): START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.ganji.android/.control.LaunchActivity} from pid 6369
12-12 17:11:24.021 I/ActivityManager( 1191): Displayed com.ganji.android/.control.LaunchActivity: +264ms
12-12 17:11:25.311 I/ActivityManager( 1191): START u0 {cmp=com.ganji.android/.control.MainActivity} from pid 5668
12-12 17:11:27.311 I/ActivityManager( 1191): Displayed com.ganji.android/.control.MainActivity: +1s989ms
冷启动时长:
12-12 17:13:16.981 I/ActivityManager( 1191): START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.ganji.android/.control.LaunchActivity} from pid 6625
12-12 17:13:18.231 I/ActivityManager( 1191): Displayed com.ganji.android/.control.LaunchActivity: +1s237ms
12-12 17:13:19.231 I/ActivityManager( 1191): START u0 {cmp=com.ganji.android/.control.MainActivity} from pid 6637
12-12 17:13:22.811 I/ActivityManager( 1191): Displayed com.ganji.android/.control.MainActivity: +3s556ms
三种场景下,都需要从start u0 cmp = LaunchActivity的时间开始,到Displayed 下一个Activity的时间结束
2、当然,也可以通过录屏的方式,录屏的命令是:
adb shell screenrecord --bugreport /sdcard/launch.mp4
之后按Ctrl+C结束,然后将视频文件导出,之后使用可按帧播放的播放器即可。
使用播放器播放的时候,可以从用户点击屏幕发白发亮的那一刻算是开始时间,这个时间可以精确到ms,之后结束的话,就以你到达的页面,并且可以等待异步加载的都完成为止。
这个与 1 中的方法相比,获取的时间是会更长一些,因为包含了点击之后有了用户的touch操作,之后 系统响应,再到系统开始响应启动进程,创建Activity,加载视图,度量并进行Activity的整体显示的过程
并且通过logcat的方式,只能用在Activity从A跳转到B的这种情况,如果是Activity不变化,只是页面数据在刷新,logcat就无法通过ActivityManager打印出日志。
最终方案确定:
最终根据我们与开发的沟通,及通过这两种方式的对比,也为了后续能够方便通过程序写成自动化的形式,选择了第一种,并且增加了通过uiautomator完成城市选择和点击back键正常退出app的UI自动化的case来支持自动化实现。
三、最后确定的方案,方案的实施和工具实现
针对以上需求:
1、方便获取竞品的对比数据,需要在同一机型上执行,并且最好执行多次,选择一个平均值
2、方便方案的通用性,以及自动化实现
通过程序实现,主要包含以下过程:
1、自动装包
2、启动logcat,清除logcat的缓存,执行 adb logcat -v time -s ActivityManager ,并将输出内容输出到某个文件
3、启动应用程序,通过adb shell am start LaunchActivity的方式,获取新装包的启动时间;之后通过uiautomator执行UI操作,完成热启动时间的获取,之后通过adb shell am force-stop pkg包名的方式杀进程,再启动完成冷启动时间的获取
3、将日志文件从手机中pull出来
4、对日志文件进行分析,因为前面都增加了-v time 的选项,log中能够打印出具体到ms的时间,可以根据你设置的规则,直接获取某条日志前面的时间
5、获取到开始时间和结束时间之后,用结束时间减去开始时间即可
备注:
参考资料————————————
APP的安装流程:
http://www.cnblogs.com/sunshine-anycall/p/3544345.html
Android APP的启动过程:
http://blog.csdn.net/freshui/article/details/8695463
http://www.androidchina.net/3851.html
APK预编译提取Odex:http://blog.csdn.net/huangyabin001/article/details/46973625
Android MultiDex实践:https://gold.xitu.io/entry/5705b2712e958a0057a5f735
Android应用打破65K方法数限制:http://www.infoq.com/cn/news/2014/11/android-multidex
Android developer的官网针对Launch-Time Performance的内容如下:https://developer.android.com/topic/performance/launch-time.html