愿历尽千帆 归来仍少年

Systrace角度: 拆解分析应用的启动流程

字数统计: 2,047阅读时长: 7 min
2021/11/11

上周看了一个支付宝启动速度逊色于对比机的案例,花了些时间整理了下这个过程。
应用启动过程中牵扯的知识点较多,基本涵盖了Android几大重要的子系统,非一篇文章能够讲清。
本文将以支付宝为例,介绍下从input事件触发到首页帧显示完成的整个流程,后面会单独成文介绍与竞品的差异分析。

本文深受androidperformance系列文章启发

测试步骤

  1. 使用user版本,开机后连接信号好的WIFI网络,清理后台所有程序。登录支付宝账号后先启动几次,最后再forceStop
  2. 手机放置五分钟后,确保机器未发热,此时打开Systrace录制,通过高速相机拍摄,点击icon冷启,再forceStop下,再次冷启,循环做五组
    取耗时最长一次925ms为例进行分析,时间范围起始点: 以手指落下接触到icon为起点,以支付宝首页内容完全加载出为结束点

Input事件

img

关键时间点

  1. AppLaunch_dispatchPtr:Up: 5s 617ms
  2. Launcher中接收到input事件: 5s 620ms
    从up事件到Launcher接收到最早的input事件耗时约3ms

这个过程大致就是:

  1. inputReader从Eventhub中获取到input事件后
  2. inputReader将事件给inputDispatcher,inputDispatcher会将事件放到 “iq” 队列中(图中没有列出)
  3. 这个时候开始寻找处理这个input事件的目标窗口了,这个案例中对应的就是Launcher,找到后放在oq队列中,等着发送给Launcher
  4. 事件发送出去后,将事件放到图中的wq队列中,等Launcher消费过这个事件后,会通知inputDisp已经消费完成,图中的wq凸起点会消失

这里顺便说下,我们平时经常会遇到input类型的ANR,对应的正是wq的等待时长。
简单理解就是事件发给应用窗口了,这个时候开始倒计时,如果5s内(我们项目上客制化为8s)应用窗口没有告知inputDispatcher这个事件消费完成,说明应用窗口无响应了,就会触发ANR。
如果5s内应用窗口处理完成了并告知inputDispatcher后,inputDispatcher就会从 “wq” 队列中及时移除待处理事件避免ANR。

创建进程

img

时间关键点:

  1. fork进程起点 5s 652ms,耗时6ms
    这里顺便介绍下关于fork进程花费时间的优化,Android R上新增了一个usap的机制,大意是会预先fork一些空进程放在池中,这样可以省去应用fork的时间。
    有些厂商会定制化这块内容,将这些预先fork出来的空进程指定包名,比如用户感知较强的微信支付宝等,这样的在支付宝冷启时,这里就不会有fork的片段,其实就是空间换时间的做法。
    但是这种做法不太适合小内存机器,相比内存的额外开销所带来的仅仅几毫秒的提速,似乎并不是特别划算。

  2. Launcher将自身pause 5s 665ms
    大概过程就是检测到前台此时resume状态的Activity是Launcher界面,就会通知桌面应用的 Activity 进入 Paused 状态,有些厂商会定制化桌面icon按压的效果,我手头的小米手机上按压会有一个置灰缩小的动画。

Launcher onpause执行完成之后,便会开始启动动画,对应的会是一连串的帧。
img

前面的内容大致对应了
点击icon->input事件的分发-> fork进程 -> Launcher onpause
launcher启动动画的过程中,支付宝开始同步执行生命周期,如果支付宝生命周期执行结束,则启动动画也会结束并切换到应用窗口

应用初始化环节

这块大致的过程是:
ZygoteInit->ActivityThreadMain->BindApplication->StartActivity->onresume->doFrame->display

对于应用开发人员而言,关注点是从BindApplication开始,这个时间点是应用开发者最早能够控制的时间点。
ZygoteInit->ActivityThreadMain

  • ZygoteInit -> 5s 669ms 标识支付宝进程初始化开始,创建binder线程池之类的事务
  • ActivityThreadMain -> 5s 670ms 创建并启动主线程的 loop 消息循环;通过 binder 调用 AMS 的 attachApplication 接口将自己 attach 注册到 AMS 中。
bindApplication

img
关键时间点
bindApplication 起始点5s 676ms,耗时112ms
bindApplication段CPU运行情况
img

bindApplication耗时拆分
img

activityStart

img

这个过程简单概述下来就是

  1. 创建 Activity 的 Context;
  2. 通过反射创建 Activity 对象;
  3. 执行 Activity 的 attach 动作,其中会创建应用窗口的 PhoneWindow 对象并设置 WindowManage;
  4. 执行应用 Activity 的 onCreate 生命周期函数,并在 setContentView 中创建窗口的 DecorView 对象;

activityStart时间段CPU执行情况
img
activityStart拆分耗时
img

activityResume

img

这个阶段主要对应的操作

  1. 执行应用 Activity 的 onResume 生命周期函数;
  2. 执行 WindowManager 的 addView 动作开启视图绘制逻辑;
  3. 创建 Activity 的 ViewRootImpl 对象;
  4. 执行 ViewRootImpl 的 setView 函数开启 UI 界面绘制动作;

img

SplashActivity首帧

市面上有很多应用打开后会有一个启动界面,这个界面叫做SplashActivity,一般会在这个页面可以做一些App数据初始化的工作。
如果应用没有SplashActivity的话,那这个环节可以直接跳过直接来到应用首页帧即可。

img
关键时间点: 启动页首帧 5s 893ms ,耗时37ms

首帧绘制结束时间点 5s931ms
img

注意一点的是: 这里的finish draw只有在mDrawsNeededToReport为0时才会打印

mDrawsNeededToReport的含义是

1
A count of the number of calls to pendingDrawFinished we require to notify the WM drawing is complete.

简单的理解这次的绘制序列结束后会打印,所以我们在首页帧后面找不到finish draw是因为首页会加载很多内容,对应的是会有很多帧才能完成,所以首页帧的结束点用finish draw并不适用。

还有一点值得注意,因为我们这个案例中是以支付宝为例的,支付宝是有SplashActivity的 ,所以Systrace中你所看到的launching: com.eg.android.AlipayGphone耗时不能代表整个启动过程。
只能粗略的代表启动到SplashActivity首帧的耗时, 但是可以在和竞品数据对比时作为一个参考指标

可以看到launching: com.eg.android.AlipayGphone共计耗费了286.6ms
img

到这里 , 从dispatchPtr:Up的5s 617ms到SplashActivity首帧绘制结束时间点5s931ms,共耗时314ms

首页帧

img

这一帧的cpu运行情况
img

cpu运行情况
img

渲染结束后将buffer queue到SF的队列中
img

冷启动结束帧

对于有源码的应用,定义结束帧会比较简单,加载哪个view或layout能够大致代表界面内容显示完全作为owner你是清楚的。
但是对于三方应用比如我们这个案例,没有其源代码,可以通过高速相机先大致确认下时间范围,然后在范围区域内的几帧逐一看。
可以先查看下支付宝首页的布局,比如这里home_advertisement.xml对应主界面上的中间广告页,这个布局的加载完成,其后面紧跟着的一帧我我们就认为是结束帧。
所以我们在对比竞品的Systrace时也要以home_advertisement.xml加载后的一帧为结束点。

img

SF侧以及渲染线程侧

img
img

关键时间点:
SF中bufferQueue对应的支付宝这一帧的buffer被消费后的时间点是 6s554ms
这个时间点可以基本认为是冷启动最后的时间点

到这里我们计算下冷启的大概耗时

  • inputReader中读取到up事件起始点 : 5s 617ms

  • 首页内容完全加载第一帧时间点: 6s554ms

整个冷启动过程共计耗时937ms,和我们高速相机测算的925ms比较接近

写在最后

到这里,对于支付宝的整个启动流程拆解结束,希望通过本文能够加深对应用启动流程的理解,后面会另行成文分析和竞品机的差异细节。

CATALOG
  1. 1. 测试步骤
  2. 2. Input事件
  3. 3. 创建进程
  4. 4. 应用初始化环节
    1. 4.1. bindApplication
    2. 4.2. activityStart
    3. 4.3. activityResume
  5. 5. SplashActivity首帧
  6. 6. 首页帧
  7. 7. 冷启动结束帧
  8. 8. 写在最后