Android踩坑经验--App启动时间正确统计姿势

在这里讨论的是指冷启动,热启动不在讨论范围内。如何正确衡量App的启动时间,一般有以下几种方法:AMS日志分析法,录屏分帧法,代码埋点法,logicat分析法等,本文重点阐述最常用的AMS日志分析法,录屏分帧法,代码埋点法,重点阐述代码埋点法的正确姿势。
1:AMS日志分析法
adb shell am start -w package_name/activity_name
输出结果如下:
$ adb shell am start -W com.xxx.xxx/com.xxx.xxx.HomeActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.xxx.xxx/.HomeActivity }
Status: ok
Activity: com.xxx.xxx/.HomeActivity
ThisTime: 496
TotalTime: 496
WaitTime: 503
Complete
WaitTime 返回从 startActivity 到应用第一帧完全显示这段时间. 就是总的耗时,包括前一个应用 Activity pause 的时间和新应用启动的时间;
ThisTime 表示一连串启动 Activity 的最后一个 Activity 的启动耗时;
TotalTime 表示新应用启动的耗时,包括新进程的启动和 Activity 的启动,但不包括前一个应用Activity pause的耗时。
开发者一般只要关心 TotalTime 即可,这个时间才是自己应用真正启动的耗时。
优缺点:这个方法简单快捷,可以作为程序员日常优化时使用,如日常有优化项可以直接运行即可看到效果,缺点是无法追踪线上的启动时间。
2:录屏分帧法
adb shell screenrecord –bugreport /sdcard/xxx.mp4
activity启动后,使用ctrl+c结束视频录制,使用adb shell screenrecord –bugreport /sdcard/xxx.mp4导出视频到电脑,使用可以按帧播放的视频软件打开(mac上quicktime就可以,win下可以用kmplayer),并按帧播放。
按帧播放视频,视频左上角会显示每一帧的时间(精确到ms)和帧数。
在这里插入图片描述
在视频中会看到icon会变暗然后高亮,高亮时就是系统开始处理本次icon点击事件了。可以把这里作为点击时间,然后根据体验要求,看到app启动页完全绘制完作为终止时间,这个时间减去点击时间就是app的启动时间。
优缺点:可以测试真实的APP启动时间,但这个方法较为复杂,需要人工计算App的启动时间,费时费力,也无法追踪线上的启动时间,只可用于线下验证测试。
目前有些公司用此方法测试,但没有采用人工测试的方式,而是采用自动化测试,通过图像识别算法,自动识别启动时间的开始和结束为止,然后计算启动时间,参考链接:https://www.testwo.com/article/1252
3:代码埋点
启动时间的代码埋点,主要是在代码中埋下时间戳,通过开始和结束时间戳计算差值,从而得出启动时间。看下目前比较流行的几个点位:
启动时间点:Application init开始,Application attachBaseContext开始,Application onCreate开始,Activity onCreate开始
结束时间点:Activity onResume结束,Activity onWindowFocusChanged结束,View dispatchDraw结束,Looper.myQueue.addIdleHandler方法开始
先拿目前手上project来看下这些点位的执行情况(里面有view post逻辑,等会会):
03-25 11:58:01.215 5559 5559 I System.out: lwl Application init
03-25 11:58:01.215 5559 5559 I System.out: lwl Application attachBaseContext
03-25 11:58:01.237 5559 5559 I System.out: lwl Application onCreate
03-25 11:58:01.395 5559 5559 I System.out: lwl Activity onCreate
03-25 11:58:01.619 5559 5559 I System.out: lwl Activity onResume
03-25 11:58:01.753 5559 5559 I System.out: lwl activity view.post
03-25 11:58:01.855 5559 5559 I System.out: lwl Activity onWindowFocusChanged
03-25 11:58:01.861 5559 5559 I System.out: lwl view dispatchDraw
03-25 11:58:02.054 5559 5559 I System.out: lwl view post
03-25 11:58:02.155 5559 5559 I System.out: lwl activity view.postDelayed
03-25 11:58:02.508 5559 5559 I System.out: lwl Looper.myQueue().addIdleHandler
启动时间点:
目前业界比较认可的是attachBaseContext方法,Application onCreate和Activity onCreate方法执行稍显不准确,至少不能在Activity onCreate时打点,很多App会在Application创建时初始化全局。
从执行时机看,在Application init方法中打点与从attachBaseContext打点基本一致。
创建应用进程时,会makeApplication,在新建Application对象时会attach context,因此调用时机Application init方法和Application attachBaseContext几乎无差的,规范性来讲,个人比较认可attachBaseContext方法
在这里插入图片描述
结束时间点:
Activity onResume结束:这个点位不合适,为什么呢?onResume完了,但首页view还没有上屏呢,还没有经过measure,layout,draw呢,因此此方法不合适。
Activity onWindowFocusChanged结束:这个方法在网上有很多人推荐,因为此方法是窗口得到或失去焦点的时候调用的借口,在源码中有一段说明:This is the best indicator of whether this activity is visible to the user.意思是是否是用户可见的最好标准。但有一个疑问,Activity可见了,但View就可见了吗?从代码中可以打断点去看,你会发现View没有上屏,Activity可见是没错,但看到的是背景而已。因此个人认为此方法还不大准确。
在这里插入图片描述
如果在核心View上打log看View的生命周期执行,会发现:
03-25 20:16:48.660 19578 19578 I System.out: lwl onMeasure
03-25 20:16:48.681 19578 19578 I System.out: lwl onMeasure
03-25 20:16:48.697 19578 19578 I System.out: lwl onLayout
03-25 20:16:48.699 19578 19578 I System.out: lwl onMeasure
03-25 20:16:48.699 19578 19578 I System.out: lwl onLayout
03-25 20:16:48.724 19578 19578 I System.out: lwl activity view.post
03-25 20:16:48.825 19578 19578 I System.out: lwl Activity onWindowFocusChanged
03-25 20:16:48.829 19578 19578 I System.out: lwl onMeasure
03-25 20:16:48.829 19578 19578 I System.out: lwl onLayout
03-25 20:16:48.834 19578 19578 I System.out: lwl view dispatchDraw
很明显View还是没上屏的(这个地方有疑问:onDraw方法没执行,但不影响分析)。
View dispatchDraw结束: 如onWindowFocusChanged中所说,如果在此方法结束的地方打断点,会发现也是白屏的。熟悉View绘制过程的都知道,先绘制根View,执行onDraw方法,再执行dispatchDraw绘制子View,子View执行onDraw,再执行子View 的dispatchDraw,继续执行孙子View声明周期,通过updateDisplayListIfDirty把View绘制相关的数据结构准备好,然后发消息给RenderThread,通知底层绘制(完全凭知识印象,感兴趣的可以找资料核实),因此这个方法算是比较接近界面可见的方法了。
Looper.myQueue.addIdleHandler方法开始:这个方法比较神奇,Android系统是基于Looper消息循环系统,通过Handler向MessageQueue投递Message,而这个方法是Looper里面的消息处理完后,会回调此方法,如果在此方法中打断点,会发现此方法执行的时候,界面可见了,看似非常符合启动时间的测试场景,但看个细节:如果我主动postDelay了一个任务,这时此方法会在postDelay的任务执行完后,再执行此回调。但其实首页早就已经可见了。因此此方法不合适。
综上所述:
推荐选择打点位置:开始:Application attachBaseContext,结束:核心View的dispatchDraw结束。
注意:打点时需注意无效时间戳的处理,比如是否加载了广告页,是否加载了引导页的等,是否是热启动等等,无效启动时间计算结果的处理,比如:计算出来启动时间结果大于5分钟则丢弃,但需上报到单独的打点中,供RD分析无效数据出现的场景。

猜你喜欢

转载自blog.csdn.net/longlong2015/article/details/88825956