Android 启动时长分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011033906/article/details/89467563

应用启动介绍

我们平时在写应用的时候,一般会指定一个 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 完全显示为准,也就是我们常说的应用第一帧.

1. 启动的类型

参考自 – Android 开发之 App 启动时间统计

  • 冷启动 ------- 当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化 Application 类,再创建和初始化 MainActivity 类,最后显示在界面上

  • 热启动 ------- 当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。热启动因为会从已有的进程中来启动,所以热启动就不会走 Application 这步了,而是直接走 MainActivity,所以热启动的过程不必创建和初始化 Application,因为一个应用从新进程的创建到进程的销毁,Application 只会初始化一次

  • 首次启动 ------- 首次启动严格来说也是冷启动,之所以把首次启动单独列出来,一般来说,首次启动时间会比非首次启动要久,首次启动会做一些系统初始化工作,如缓存目录的生产,数据库的建立,SharedPreference的初始化,如果存在多 dex 和插件的情况下,首次启动会有一些特殊需要处理的逻辑,而且对启动速度有很大的影响,所以首次启动的速度非常重要,毕竟影响用户对 App 的第一映像。

2. 统计启动时间

adb 统计及其参数介绍

具体细节请参考: https://www.androidperformance.com/2015/12/31/How-to-calculation-android-app-lunch-time/

adb shell am start -w pageage/activityname  
//通过这条命令启动,可以获得启动时间。
$ adb shell am start -W com.abc.test/com.abc.test.MainActivity
Starting: Intent { act=android.intent.action.MAIN cat=
Status: ok
Activity: com.speed.test/.HomeActivity
ThisTime: 496
TotalTime: 496
WaitTime: 503
Complete

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

  • startTime 记录的刚准备调用 startActivityAndWait()的时间点

  • endTime 记录的是 startActivityAndWait() 函数调用返回的时间点

    扫描二维码关注公众号,回复: 5993663 查看本文章
  • WaitTime = startActivityAndWait() 调用耗时。

  • WaitTime 就是总的耗时,包括前一个应用 Activity pause 的时间和新应用启动的时间;

  • ThisTime 表示一连串启动 Activity 的最后一个 Activity 的启动耗时;

  • TotalTime 表示新应用启动的耗时,包括新进程的启动和 Activity 的启动,但不包括前一个应用 Activity pause 的耗时。也就是说,开发者一般只要关心 TotalTime 即可,这个时间才是自己应用真正启动的耗时。

总结下

  • 如果只关心某个应用自身启动耗时,参考 TotalTime
  • 如果关心系统启动应用耗时,参考 WaitTime
  • 如果关心应用界面 Activity 启动耗时,参考 ThisTime

3. 起始时间点

  • 冷启动启动时间一般可以在 Application.attachBaseContext() 开始的位置记录起始时间点,因为在这之前 Context 还没有初始化,一般也干不了什么事情,当然这个是要视具体情况来定,其实只要保证在 App 的具体业务逻辑开始执行之前记录起始时间点即可。

  • 热启动启动时间点可以在 Activity.onRestart() 中记录起始时间点。

4. 结束时间点

结束时间点理论上要选在 App 显示出第一屏界面的时候,但是在什么位置 App 显示出第一屏界面呢?网上很多文章说在 Activity 的 onResume 方法执行完成之后,Activity 就对用户可见了,实际上并不是,一个 Activity 走完onCreate onStart onResume 这几个生命周期之后,只是完成了应用自身的一些配置,比如 Activity 主题设置 window 属性的设置 View 树的建立,但是其实后面还需要各个 View 执行 measure layout draw等。所以在 OnResume 中记录结束时间点的 Log 并不准确,大家可以注意一下上面流程中最后一个函数 Activity.onWindowFocusChanged,下面是它的注释:

/**
*Called when the current {@link Window} of the activity gains or loses
* focus.  This is the best indicator of whether this activity is visible
* to the user.  The default implementation clears the key tracking
* state, so should always be called.
...
*/

通过注释我们可以看到,这个函数是判断 activity 是否可见的最佳位置,所以我们可以Activity.onWindowFocusChanged 记录应用启动的结束时间点,不过需要注意的是 该函数在 Activity 焦点发生变化时就会触发,所以要做好判断,去掉不需要的情况。

5. 应用的主要启动流程

  • 通过 Launcher 启动应用时,点击应用图标后,Launcher 调用 startActivity 启动应用。
    Launcher Activity 最终调用 InstrumentationexecStartActivity 来启动应用。

  • Instrumentation 调用 ActivityManagerProxy (ActivityManagerService 在应用进程的一个代理对象) 对象的 startActivity 方法启动 Activity

  • 到目前为止所有过程都在 Launcher 进程里面执行,接下来 ActivityManagerProxy 对象跨进程调用 ActivityManagerService (运行在 system_server 进程)的 startActivity 方法启动应用。

  • ActivityManagerServicestartActivity 方法经过一系列调用,最后调用 zygoteSendArgsAndGetResult 通过 socket 发送给 zygote 进程,zygote 进程会孵化出新的应用进程。

  • zygote 进程孵化出新的应用进程后,会执行 ActivityThread 类的 main 方法。在该方法里会先准备好 Looper 和消息队列,然后调用 attach 方法将应用进程绑定到 ActivityManagerService,然后进入 loop 循环,不断地读取消息队列里的消息,并分发消息。

  • ActivityManagerService 保存应用进程的一个代理对象,然后 ActivityManagerService 通过代理对象通知应用进程创建入口 Activity 的实例,并执行它的生命周期函数。

总结过程就是

  1. 用户在 Launcher 程序里点击应用图标时,会通知 AMS 启动应用的入口 Activity
  2. AMS 发现这个应用还未启动,则会通知 Zygote 进程孵化出应用进程,然后在这个应用进程里执行 ActivityThreadmain 方法
  3. 应用进程接下来通知 AMS应用进程已启动,AMS保存应用进程的一个代理对象,这样 AMS可以通过这个代理对象控制应用进程
  4. 然后 AMS 通知应用进程创建入口 Activity 的实例,并执行它的生命周期函数。

6. 冷启动的流程

-> Application 构造函数
-> Application.attachBaseContext()
-> Application.onCreate()
-> Activity 构造函数
-> Activity.setTheme()
-> Activity.onCreate()
-> Activity.onStart
-> Activity.onResume
-> Activity.onAttachedToWindow
-> Activity.onWindowFocusChanged

7. Android 启动优化

应用在冷启动之前,要执行三个任务:

  • 加载启动App;
  • App启动之后立即展示出一个空白的Window;
  • 创建App的进程;

而这三个任务执行完毕之后会马上执行以下任务:

  • 创建App对象;
  • 启动Main Thread;
  • 创建启动的Activity对象;
  • 加载View;
  • 布置屏幕;
  • 进行第一次绘制;

而一旦App进程完成了第一次绘制,系统进程就会用 MainActivity 替换已经展示的 Background Window,此时用户就可以使用 App了。

在这里插入图片描述
作为普通应用,App 进程的创建等环节我们是无法主动控制的,可以优化的也就是 ApplicationActivity 创建以及回调等过程。

同样,Google也给出了启动加速的方向:

  • 利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验;
  • 避免在启动时做密集沉重的初始化(Heavy app initialization)
  • 定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等。

3、通用应用启动加速套路?

  • 利用主题快速显示界面;
  • 异步初始化组
  • 梳理业务逻辑,延迟初始化组件、操作
  • 正确使用线程
  • 去掉无用代码、重复逻辑等

具体操作请看这里:Android性能优化之启动加速35%

参考链接

猜你喜欢

转载自blog.csdn.net/u011033906/article/details/89467563