目录
- app启动分类
- app启动时间统计
- app启动主要流程
-
问题
1:App冷启动时间到底是如何计算、触发的?
2:如何实时统计App冷启动时间?
app启动分类
- 冷启动
- 热启动
- 温启动
冷启动
- 定义:通过Launcher点击应用图标或者在第三方启动一个目标app,如果“App所运行的进程未创建,则需要先通过Zygote进程fork子进程,然后再执行Application的创建、根Activity的创建、根视图树的解析、绘制等操作”。
-
场景:
- 设备启动以来首次启动应用程序。
- 应用的进程被系统kill掉了,进程没有缓存在主存中。点击应用图标需要重新走一遍app启动的整体流程。
-
冷启动的事件组成:从整体看,冷启动过程主要分为“系统为启动app做准备”与“app进程启动完毕,继续后续的app进程内部操作”
-
系统为启动app做准备
- “加载并启动应用程序”:Launcher与SystemServer进程中的AMS通信过程。
- “启动后立即显示应用程序的空白启动窗口”:AMS从Intent解析目标Activity,并根据Manifest注册的Activity生成ActivityRecord,把该ActivityRecord 添加到ActivityStack中。根据指定的Theme,显示其中定义的"预览窗口"。
- “创建应用程序进程” :AMS如果检测到app运行在的进程未创建,则需要调用AMS.startProcessLocked()通知Zygite fork子进程。
-
app进程内部操作
- 启动主线程:app进程创建完毕后,会执行ActivityThread的main()用于启动app进程内的主线程。
- 创建Application:app进程创建完毕,会在main()中执行其attach(),经过层层调用会调用ActivityThread的bindApplication:会向ActivityThread的Handler中发送一个BIND_APPLICATION消息。等Handler接收到消息之后,会执行ActivityThread的handleBindApplication()实例化Application并执行相应生命周期方法。
- 创建根Activity:等Application对象初始化完毕之后,会调用ASS.realStartActivityLocked(),该方法内部经过层层调用会向ActivityThread中的Handler发送一个LAUNCH_ACTIVITY消息,然后进行目标Activity初始化以及执行其相应生命周期方法。
- 解析视图树:一般的我们会在onCreate()中通过setContentView()方式设置要显示的 视图布局。该视图布局通过LayoutInflate进行inflate()并添加到DecorView中。
- 布局屏幕:等添加完视图树后,会在ActivityThread的handleResumeActivity()执行WindowManager的addView(),该操作用来通知运行在SystemServer进程的WMS服务,用来“添加Window中设置的DecorView”。等添加View完毕,会执行WM的updateViewLayout(),该方法经过层层调用会从WindowManagerGlobal中找到与目标View绑定的ViewRootImpl,然后通过ViewRootImpl内部的“编舞(信号处理程序)”来注册用于接收“Vsync信号”,等接收到此信号后会执行performTraversals(),该方法内部会执行整棵视图树的measure、layout、draw操作。
- 执行初始绘制
-
系统为启动app做准备
冷启动流程图
热启动
- 定义:如果应用程序的进程仍然缓存在内存中,热启动只是 把后台进程切换至前台。
- 优点:热启动比冷启动过程简单且开销低。
具体的:应用从后台切换至前台的操作,因为app还没被kill掉,application以及相应Activity没有被destory掉,这样就不会重新执行Activity的创建、初始化以及视图树的解析、measure、layout、draw操作。
app启动时间统计
-
通过系统log:
-
app启动过程中,会通过logcat打印一个包含"Displayed"的log信息。
该信息包含的是:从初始化Activity组件、执行onCreate/onStart()/onResume()等相应生命周期方法、Activity组件设置的视图树第一次绘制到屏幕上之后,这3步所花费的时间。 -
该log信息中必定会包含一个"Display Time",可能包含一个"Total Time"。
1.png
2.png -
Display-Time
-
如果,app运行的进程已存在,则表示“启动目标Activity并首次完成视图树的绘制” 所花费的时间。该时间具体统计的是:
- Create and initialize the activity.
- Inflate the layout.
- Draw your application for the first time.
-
如果,app运行的进程还未创建,则表示“从launcher点击app开始,到相应Activity的layout首次绘制完毕”,该时间就是“Total-Time”统计的时间。具体的该时间统计的是以下事件花费的时间:
- Launch the process.
- Initialize the objects.
- Create and initialize the activity.
- Inflate the layout.
- Draw your application for the first time.
-
-
Total-Time
- 含义:“app启动完毕所花费的总时间。该时间大于等于Display-Time”。
-
统计的事件:
- Launch the process.
- Initialize the objects.
- Create and initialize the activity.
- Inflate the layout.
- Draw your application for the first time.**
-
产生条件:
-
如果在app启动过程中,包括“另一个首先启动但未向屏幕显示任何内容的Activity()”,此时就会在log中打印出“Total-Time”。具体理解如下:
- 首先,启动的Activity不是最终要展示的Activity。
好多应用的根Activity一般用于“闪屏显示”,等在这个页面显示一段广告或者其他内容之后才会跳转至“主页面”。 - 其次,无论此Activity是否通过setContentView()设置了要显示的视图树,只要“在一定时间内通过startActivity()跳转至app的“主页面”了(此处需要满足:在跳转之前,闪屏页的视图树还没来得及通过SurfaceFlinger显示在屏幕上)。
- 首先,启动的Activity不是最终要展示的Activity。
-
此时不会显示2行启动时间的log,只会显示一行关于“启动至主页”的时间log,且该log中会包含“Total-Time”。那么此时的“Total-Time”表示app启动完毕所花费的时间。 此时的“Display-Time”只是统计了:“主Activity创建以及第一次绘制完该Activity的视图树的时间”。
-
-
如何让系统打印出此时间:
就如“产生条件-1.2
”中描述的那样 ,具体的可以:在onCreate()或者其他生命周期方法中启动那个最终要显示的Activity”,这样就会把“Total-Time”打印出来。
3.jpg
-
-
通过adb shell脚本启动app
time2.png
这种方式获取的时间与 第一种方式 从logcat中获取的时间一致。
-
Display/Total-Time是在哪统计的?
具体的是通过ActivityRecord.reportLaunchTimeLocked()输出到logcat。- 源码:
private void reportLaunchTimeLocked(final long curTime) {
//ActivityRecord记录的是“已经push到ActivityTask中的Activity信息”。
//它是Activity栈中存储的数据的“基本元素”。
final ActivityStack stack = getStack();
if (stack == null) {
return;
}
final long thisTime = curTime - displayStartTime;
final long totalTime = stack.mLaunchStartTime != 0
? (curTime - stack.mLaunchStartTime) : thisTime;
if (SHOW_ACTIVITY_START_TIME) {
.......
StringBuilder sb = service.mStringBuilder;
sb.setLength(0);
sb.append("Displayed ");
sb.append(shortComponentName);
sb.append(": ");
TimeUtils.formatDuration(thisTime, sb);
if (thisTime != totalTime) {
sb.append(" (total ");
TimeUtils.formatDuration(totalTime, sb);
sb.append(")");
}
Log.i(TAG, sb.toString());
}
.......
}
-
参数说明:
- curTime:传入的,用于标识“何时调用的该方法”。
- displayStartTime:用于记录“启动当前Activity的时间点”。该变量会在ActivityTask.setLaunchTime()被赋值,而setLaunchTime()方法又是在ASS调用startSpecificActivitylocked()用于实际启动一个目标Activity内部被执行的。
- stack.mLaunchStartTime:Activity栈中通过mLaunchStartTime记录根Activity的启动时间。
- thisTime:表示“当前Activity从启动到第一帧绘制完毕的时间”。
- totalTime:表示“app从创建进程至目标Activity启动到绘制完第一帧的时间”。
该时间就是“目标app的冷启动时间”。 - waitTime:表示总耗时(包括系统耗时+app启动总耗时),是从“Launcher点击应用图标开始,到App启动结束”这段时间内的耗时。
总结:
1:thisTime:表示“当前Activity从启动到第一帧绘制完毕的时间”。
2:totalTime:表示“app从创建进程至栈顶Activity启动到绘制完第一帧的时间”。该时间就是“目标app的冷启动时间”。
3:如果启动的Activity不是栈顶Activity,且启动的Activity没有要显示的视图树,那么thisTime统计的时长是不等于totalTime统计的时长的。
3.1:此种情况下,thisTime只是统计的是“最后一个Activity也就是栈顶Activity”的启动到绘制完第一帧的时长。
3.2:如果最终启动的Activity就是栈顶Activity,也就是说“启动的是根Activity”此种情况下thisTime统计的时长与totalTime一致。
4:waitTime:表示“总耗时”,其中包括 系统创建app进程前的操作耗时+app启动总耗时。
5:waitTime、totalTime、thisTime 3者时长关系:waitTime>totalTime>=thisTime。
6:displat-time:logcat中输出的displatTime就是thisTime。
7:total-time:logcat中输出的totle就是totlaTime。
App启动主要流程(详细介绍请戳这里)
app启动流程总体上分为:
-
系统为启动app做准备(主要是Launcher进程与SystemServer进程交互):
- 通过Intent解析待启动的根Activity组件。
- 创建一个新的Activity栈(TaskRecord)并把与该Activity对应的ActivityRecord压栈。
- 获得Activity组件中声明的theme并显示“预览窗口”。
-
创建App进程:
- AMS接收到Launcher发送的请求后,检测到app进程未创建。通过AMS.startProcessLocked()先向Zygote进程发送Socket请求创建子进程(IPC:SystemServer进程与Zygote进程通信)。
- Zygote接收到AMS发送的请求后,fork子进程、创建Binder线程池、初始化虚拟机。
-
App进程内部操作:
- 启动主线程:Zygote进程启动完app进程后,App进程内部会执行ActivityThread的main()用来启动主线程、做一些初始化操作(IPC:Zygote进程与SystemServer通信用于fork子进程。main()中主要是做的ActivityThread的初始化操作,其中包括初始化ApplicationThread类型的属性“mAppThread”)。
- 创建Application:在main()内部初始化ActivityThread之后会执行其attach(),该方法内部经过层层调用最终会执行ActivityThread.bindApplication(),该方法会向ActivitThread中的H类型的Handler中发送一个BIND_APPLICATION消息,在接收到该消息后会,创建ContextImpl实例、实例化Application并执行Application的attach(),onCreate()等方法。
- 创建根Activity:执行ASS.realStartActivityLocked(),其内部会调用app进程的ApplicationThread类中的scheduleLaunchActivity()创建、启动Application(Application在第4步已经创建了,则此方法中不会再创建进而也不会执行Application的生命周期方法)、Activity,并执行相应生命周期方法。
- 添加以及绘制视图树到Window:app进程通过WindowManager的addView()通知AMS添加Window,并通过WindowManager的updateViewLayout()执行View树的meausre、layout、draw等操作。(addView()操作涉及到IPC,client端是app进程,SystemServer进程是服务端)。
问题
1:App冷启动时间到底是如何计算、触发的?
参考:
https://developer.android.com/topic/performance/vitals/launch-time
https://blog.csdn.net/qian520ao/article/details/81908505
https://www.androidperformance.com/2015/12/31/How-to-calculation-android-app-lunch-time/
网友评论