怎么计算apk的启动时间?

利用python或者直接用adb命令怎么计算apk的启动时间呢?就是计算从点击图标到apk完全启动所花费的时间。比如,对游戏来说就是点击游戏图标到进入…
关注者
574
被浏览
41,179

12 个回答

事实上是可以准确计算的.但是要分场景.

但是要分开游戏和应用. 大家都知道,在Android中,游戏开发和应用开发是两码事.所以我们需要分开来说.

1. 应用启动.

我们平时在写应用的时候,一般会指定一个mainActivity,用户在桌面上点击这个Activity的时候,系统会直接起这个Activity. 我们知道Activity在启动的时候会走onCreate/onStart/onResume.这几个回调函数.

许多书里讲过,当执行完onResume函数之后,应用就显示出来了...其实这是一种不准确的说法,因为从系统层面来看,一个Activity走完onCreate/onStart/onResume这几个生命周期之后,只是完成了应用自身的一些配置,比如window的一些属性的设置/ View树的建立(只是建立,并没有显示,也就是说只是调用了inflate而已) . 后面ViewRootImpl还会调用两次performTraversals ,初始化Egl以及measure/layout/draw.等.

所以我们定义一个Android应用的启动时间, 肯定不能在Activity的回调函数上下手.而是以用户在手机屏幕上看到你在onCreate的setContentView中设置的layout完全显示为准,也就是我们常说的应用第一帧.

上面扯得有点远,不感兴趣的话可以不看,下面直接说方法.

题主说的adb shell am start -w packagename/activity,是可以完全应用的启动时间的.不过也要分场景.

1.1 应用第一次启动

也就是我们常说的冷启动,这时候你的应用程序的进程是没有创建的. 这也是大部分应用的使用场景.用户在桌面上点击你应用的icon之后,首先要创建进程,然后才启动mainActivity.

这时候adb shell am start -w packagename/activity 返回的结果,就是标准的应用程序的启动时间:

➜ adb shell am start -W com.meizu.media.painter/com.meizu.media.painter.PainterMainActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.meizu.media.painter/.PainterMainActivity }
Status: ok
Activity: com.meizu.media.painter/.PainterMainActivity
ThisTime: 355
TotalTime: 355
WaitTime: 365
Complete

总共返回了三个结果,我们以WaitTime为准. (关于ThisTime/TotalTime/WaitTime的区别,各位可以自己去看源码.这里就不详细说了). WaitTime会返回从startActivity到应用第一帧完全显示这段时间.

1.2 应用非第一次启动

如果是你按Back键,并没有将应用进程杀掉的话,那么执行上述命令就会快一些,因为不用创建进程了,只需要启动一个Activity即可。这也就是我们说的应用热启动。

2. 游戏启动

游戏启动的话,就不适用用命令行的方法来启动了,因为从用户点击桌面图标到登录界面,既有系统的部分也有游戏自己的部分。

2.1 系统部分

游戏也有一个Activity,所以启动的时候还是会去启动这个Activity,所以系统启动部分也就是用户点击桌面桌面响应到这个Activity启动。

2.2 游戏部分

一般游戏的主Activity启动后,还会做一些比较耗时的事情,这时候你看到的界面是不能操作的,比如:加载游戏数据、联网更新数据、读取和更新配置文件、游戏引擎初始化等操作。从游戏开发的角度来看,到了真正用户能操作的界面才算是一个游戏真正加载完成的时间。

那么这个时间,就得使用Log来记录了,因为加载游戏数据、联网更新数据、读取和更新配置文件、游戏引擎初始化这些操作,都是游戏自己的逻辑,与系统无关,所以得由游戏自己定义加载完成的点。

3. 准确性

计算机最让人着迷的一点就是其准确性,1+1永远等于2,启动耗时多久就是多久,每一次可能不一样,但每一次的时间都是这一次的准确时间。

一些愚见,有想法可以一起交流

终于有空来填坑啦。关于应用启动速度,@

Gracker

已经回答的很完善了。我补充下“adb shell am start -W ”这条命令得出的三个时间是如何计算出来的,了解了这个也就清楚了究竟哪个时间更准了。

“adb shell am start -W ”的实现在frameworks\base\cmds\am\src\com\android\commands\am\Am.java文件中。其实就是跨Binder调用ActivityManagerService.startActivityAndWait()接口(后面将ActivityManagerService简称为AMS),这个接口返回的结果包含上面打印的ThisTime、TotalTime时间。

WaitTime = endTime - startTime

startTime记录的刚准备调用startActivityAndWait()的时间点,endTime记录的是startActivityAndWait()函数调用返回的时间点,WaitTime = startActivityAndWait()调用耗时。

ThisTime、TotalTime的计算在frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java文件的reportLaunchTimeLocked()函数中。

我们来解释下代码里curTime、displayStartTime、mLaunchStartTime三个时间变量。

curTime表示该函数调用的时间点.

displayStartTime表示一连串启动Activity中的最后一个Activity的启动时间点.

mLaunchStartTime表示一连串启动Activity中第一个Activity的启动时间点.

正常情况下点击桌面图标只启动一个有界面的Activity,此时displayStartTime与mLaunchStartTime便指向同一时间点,此时ThisTime=TotalTime。另一种情况是点击桌面图标应用会先启动一个无界面的Activity做逻辑处理,接着又启动一个有界面的Activity,在这种启动一连串Activity的情况下(知乎的启动就是属于这种情况),displayStartTime便指向最后一个Activity的开始启动时间点,mLaunchStartTime指向第一个无界面Activity的开始启动时间点,此时ThisTime!=TotalTime。这两种情况如下图:

在上面的图中,我用①②③分别标注了三个时间段,在这三个时间段内分别干了什么事呢?

在第①个时间段内,AMS创建ActivityRecord记录块和选择合理的Task、将当前Resume的Activity进行pause;

在第②个时间段内,启动进程、调用无界面Activity的onCreate()等、pause/finish无界面的Activity;

在第③个时间段内,调用有界面Activity的onCreate、onResume;

看到这里应该清楚 ThisTime、TotalTime、WaitTime三个时间的关系了吧。WaitTime就是总的耗时,包括前一个应用Activity pause的时间和新应用启动的时间;ThisTime表示一连串启动Activity的最后一个Activity的启动耗时;TotalTime表示新应用启动的耗时,包括新进程的启动和Activity的启动,但不包括前一个应用Activity pause的耗时。也就是说,开发者一般只要关心TotalTime即可,这个时间才是自己应用真正启动的耗时

Event log中TAG=am_activity_launch_time中的两个值分表表示ThisTime、TotalTime,跟通过“adb shell am start -W ”得到的值是一致的。

最后再说下系统根据什么来判断应用启动结束。我们知道应用启动包括进程启动、走Activity生命周期onCreate/onResume等。在第一次onResume时添加窗口到WMS中,然后measure/layout/draw,窗口绘制完成后通知WMS,WMS在合适的时机控制界面开始显示(夹杂了界面切换动画逻辑)。记住是窗口界面显示出来后,WMS才调用reportLaunchTimeLocked()通知AMS Activity启动完成。

最后总结一下,如果只关心某个应用自身启动耗时,参考TotalTime;如果关心系统启动应用耗时,参考WaitTime;如果关心应用有界面Activity启动耗时,参考ThisTime