计算apk的启动时间并且进行优化

来源:互联网 发布:恶搞软件制作 编辑:程序博客网 时间:2024/06/07 18:07

1,何为apk的启动(即就是应用的启动)

分为两种启动.冷启动也就是apk第一次启动,没有在系统中创建进程(冷启动需要走application类进行初始化,而热启动则不需要再走了).热启动顾名思义就是已经存在了进程(例如启动之后直接的返回(home键)或者back),两者的启动时间是有很大的差别的.(在已经启动之后需要进行冷启动就是将进程杀死,使用数据清除项或者是其他的杀死功能即可)

应用启动和游戏启动是不一样的.应用指的就是点击icon之后到主界面(欢迎界面)的显示(我们肉眼看到为止).而游戏指的是点击到登录为止.

以上我们说的启动就是点击icon之后的操作,并不是在最近使用的列表中选择程序这样子叫做回复,不属于我们讨论的范畴

一般来说用户有杀死最近使用程序的习惯,所以一般来讲我们都是冷启动较多的.

2,计算启动时间

2,1 明确启动的流程

在安卓系统上,应用在没有进程的情况下,应用的启动都是这样一个流程:当点击app的启动图标时,安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用,之后会依次创建和初始化Application类、创建MainActivity类、加载主题样式Theme中的windowBackground等属性设置给MainActivity以及配置Activity层级上的一些属性、再inflate布局、当onCreate/onStart/onResume方法都走完了后最后才进行contentView的measure/layout/draw显示在界面上(这一部分正确吗????),所以直到这里,应用的第一次启动才算完成,这时候我们看到的界面也就是所说的第一帧。

所以,总结一下,应用的启动流程如下:

Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。

2.2 计算启动的时间方法1

应用:adb shell am start -W com.magugi.enterprise/com.magugi.enterprise.index.ui.MainActivity(包名/到指定页的显示),命令之前应该回到主界面无论此时是冷启动还是热启动.我们以WaitTime为准即可,就是我们应用启动所花费的时间.可能我们每一次获取到的时间不同但是相信每一次肯定是准确的.

ThisTime/TotalTime/WaitTime的区别

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

  • 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 启动完成。 所以说并不是我们常说的在onresume的时候我们就会看到界面,这是一种错误的认识.

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


2.3 计算启动的时间方法2

其实方法1只是知道了启动的时间但是并不知道启动的时间是花在哪里的,需要我们进行打log,然后再去分析日志才能知道时间具体是花在了什么地方,但是此方法使用ddms的分析trace文件可以具体的知道我们的时间到底是花在了什么地方

在application的oncreate方法首尾

Debug.startMethodTracing("GithubApp");...Debug.stopMethodTracing();
运行一遍程序之后将会在sdcard上生成文件的,我们可以进行导出

adb pull /sdcard/stylist.trace c:\Users\will\Desktop\stylist.trace

然后再打开ddms,file-->open file将我们导出的文件放进去进行分析


在下方的方法区点击"Real Time/Call", 按照方法每次调用耗时降序排.
耗时超过500ms都是值得注意的.
看左边的方法名, 可以看到耗时大户就是我们用的几大平台的初始化方法, 特别是Bugly, 还加载native的lib, 用ZipFile操作等.时间证明bugly已经做的不错了,但是umeny花的时间是最长的,其次就是我们sp文件的初始化
点击每个方法, 可以看到其父方法(调用它的)和它的所有子方法(它调用的).
点击方法时, 上方的该方法执行时间轴会闪动, 可以看该方法的执行线程及相对时长

3,优化启动时间

3.1、在Application的构造器方法、attachBaseContext()、onCreate()方法中不要进行耗时操作的初始化,一些数据预取放在异步线程中,可以采取Callable实现(主要就是浪费在了初始化一些组件上面)

使用IntentService来进行初始操作

2、对于sp的初始化,因为sp的特性在初始化时候会对数据全部读出来存在内存中,所以这个初始化放在主线程中不合适,反而会延迟应用的启动速度,对于这个还是需要放在异步线程中处理。
3、对于MainActivity,由于在获取到第一帧前,需要对contentView进行测量布局绘制操作,尽量减少布局的层次,考虑StubView的延迟加载策略,当然在onCreate、onStart、onResume方法中避免做耗时操作。
4,增加体验:

对于应用的启动时间,只能是尽量的避免一些耗时的、非必要的操作在主线程中,这样相对可以缩减一部分启动的耗时,另外一方面在等待第一帧显示的时间里,可以加入一些配置以增加体验,比如加入Activity的background,这个背景会在显示第一帧前提前显示在界面上。 
1、先为主界面单独写一个主题style,设置一张待显示的图片,这里我设置了一个颜色,然后在manifest中设置给MainActivity:

<style name="AppTheme.Launcher"> <item name="android:windowBackground">@drawable/bule</item></style>//...  <activity   android:name=".MainActivity"   android:label="@string/app_name"   android:theme="@style/AppTheme.Launcher">   <intent-filter>    <action android:name="android.intent.action.MAIN" />    <category android:name="android.intent.category.LAUNCHER" />   </intent-filter>  </activity>

2、(若是不一样的话需要重新的设置,一样的话省略此步骤即可)然后在MainActivity中加载布局前把AppTheme重新设置给MainActivity:

@Override protected void onCreate(Bundle savedInstanceState) {  setTheme(R.style.AppTheme);  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);}
这样在启动时会先显示background,然后待界面绘制完成再显示主界面:


原创粉丝点击