cover_image

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

Steven Li 酷派技术团队
2021年11月11日 10:56

上周看了一个支付宝启动速度逊色于对比机的案例,花了些时间整理了下这个过程。

应用启动过程中牵扯的知识点较多,基本涵盖了Android几大重要的子系统,一篇文章能够讲清。

本文不会介绍过多细节,假设你已经熟悉如下内容

  • 熟悉input事件处理流程

  • 熟悉进程fork & 应用组件的初始化流程

  • 熟悉应用 UI 布局与绘制

  • 熟悉渲染线程 & SurfaceFlinger & HWC的交互流程


本文将以支付宝为例,介绍下从input事件触发到首页帧显示完成的整个流程,后面会单独成文介绍与竞品的差异分析。


测试步骤
  1. 使用user版本,开机后连接信号好的WIFI网络,清理后台所有程序。登录支付宝账号后先启动几次,最后再forceStop

  2. 手机放置五分钟后,确保机器未发热,此时打开Systrace录制,通过高速相机拍摄,点击icon冷启,再forceStop下,再次冷启,循环做五组


取耗时最长一次925ms为例进行分析,时间范围起始点:

以手指落下接触到icon为起点,以支付宝首页内容完全加载出为结束点


Input事件


图片

关键时间点

  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。


创建进程


图片

时间关键点: 

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执行完成之后,便会开始启动动画,对应的会是一连串的帧。

图片

前面的内容大致对应了

点击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

图片

关键时间点

bindApplication 起始点5s 676ms,耗时112ms


bindApplication段CPU运行情况

图片

bindApplication耗时拆分

图片




activityStart

图片

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

  1. 创建 Activity 的 Context;

  2. 通过反射创建 Activity 对象;

  3. 执行 Activity 的 attach 动作,其中会创建应用窗口的 PhoneWindow 对象并设置 WindowManage;

  4. 执行应用 Activity 的 onCreate 生命周期函数,并在 setContentView 中创建窗口的 DecorView 对象;


activityStart时间段CPU执行情况

图片

activityStart拆分耗时

图片



activityResume

图片

这个阶段主要对应的操作

  1. 执行应用 Activity 的 onResume 生命周期函数;

  2. 执行 WindowManager 的 addView 动作开启视图绘制逻辑;

  3. 创建 Activity 的 ViewRootImpl 对象;

  4. 执行 ViewRootImpl 的 setView 函数开启 UI 界面绘制动作;

图片




SplashActivity首帧

市面上有很多应用打开后会有一个启动界面,这个界面叫做SplashActivity,一般会在这个页面可以做一些App数据初始化的工作。

如果应用没有SplashActivity的话,那这个环节可以直接跳过直接来到应用首页帧即可。

图片

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


首帧绘制结束时间点   5s931ms

图片

注意一点的是:

这里的finish draw只有在mDrawsNeededToReport为0时才会打印

mDrawsNeededToReport的含义是

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首帧的耗时, 但是可以在和竞品数据对比时作为一个参考指标


launchingcom.eg.android.AlipayGphone 286.6ms

图片

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


首页帧


图片

这一帧的拆分耗时

图片

这一帧的cpu运行情况

图片

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

图片


冷启动结束帧

对于有源码的应用,定义结束帧会比较简单,加载哪个view或layout能够大致代表界面内容显示完全作为owner你是清楚的。

但是对于三方应用比如我们这个案例,没有其源代码,可以通过高速相机先大致确认下时间范围,然后在范围区域内的几帧逐一看。

可以先查看下支付宝首页的布局,比如这里home_advertisement.xml对应主界面上的中间广告页,这个布局的加载完成,其后面紧跟着的一帧我我们就认为是结束帧。

所以我们在对比竞品的Systrace时也要以home_advertisement.xml加载后的一帧为结束点。


图片

SF侧以及渲染线程侧

图片

图片

关键时间点: 

SF中bufferQueue对应的支付宝这一帧的buffer被消费后的时间点是 6s554ms

这个时间点可以基本认为是冷启动最后的时间点


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

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

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


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


写在最后

笔者曾经梳理过这块的流程,自以为明白了,但是本次梳理的过程中发现有些地方,并不能做到自圆其说,究其根本还是没有真正理解。

这块的内容也绝非网上看几篇文章就能理解,唯有亲身实际梳理过这块的流程,并且能够在脱离文档的情况下清晰准确的讲述出来,才能算的上是真正的理解。

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



继续滑动看下一个
酷派技术团队
向上滑动看下一个