Fork me on GitHub

Android 性能专题 - 启动优化(一)启动耗时

声明:本文是《Android 性能优化入门与实战》——张世欣著 的笔记

为什么要做启动优化

App 启动耗时每减少 1s,用户流失率降低 6.9%

启动监控

1
2
3
4
5
6
7
8
9
补充知识

**App 冷温热启动分辨**:根据进程、Activity 是否已经存在

**冷启动**: 进程初始化 -> Activity.onCreate -> Activity.onStart

**温启动**: Activity.onCreate -> Activity.onStart

**热启动**:Activity.onStart

监控数据设计

需要比较的对照组:

  1. 旧版本-新版本
  2. 冷启动-温启动-热启动

数据与目的:

  1. 获取总耗时,判断新版本更快还是更慢
  2. 获取各区间耗时,具体分析耗时到具体区间

App 启动代码顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Application 构造函数

Application#attachBaseContext

ContentProvider#onCreate

Application#onCreate

Activity#onStart

Activity#onResume

View#onDraw

Activity#onWindowFocusChanged

起点:Application 构造函数

终点:

方案一:MainActivity 的某个 View 的第一次 onDraw(绘制函数)

- 优点:可以拿到第一帧绘制的耗时
- 缺点:
1. 执行时第一帧还没绘制完成
2. 需要选择某个核心布局,业务改造的时候容易影响到启动监控逻辑

方案二:MainActivity 的 onWindowFocusChanged

- 优点:不受业务逻辑的影响
- 缺点:被调用的时可能已经不是首帧,会将非首帧的时间算入

获取启动各阶段耗时

方法:

  1. 手动埋点
  2. 编译时 AOP

获取启动性能数据

1
2
3
4
5
6
分析 App 启动慢的几个重要指标
-> 代码耗时 (判断标准:App 运行的 CPU 时间充足)
-> 获取的 CPU 时间不足 (判断标准:App 运行的 CPU 时间不足)
-> 线程优先级不够 (判断标准:主线程优先级)
-> 被其他线程抢占过多 (判断标准:主线程被抢占次数)
-> 内存不足 (判断标准:启动期间 GC 执行次数和耗时)

线下分析

Logcat 或者 adb logcat 中查看关键字 Displayed 相关日志

自动执行 App的启动并获取启动耗时:通过 adb shell am start 实现多次自动启动 App 并获取第一次的启动耗时:

1
adb shell am start -S -W -R -3 com.antfortune.wealth/com.alipay.mobile.quinox.LauncherActivity
  • am start 是 ActivityManagerService 提供的命令,用来启动 Activity。
  • - S 即 Stop,表示在每次启动前,先强制停止 App 运行,以实现冷启动。
  • - W 即 Wait,表示执行后等待启动完成再退出,以统计整个启动的耗时。
  • - R 即 Repeat, 表示重复执行启动的次数,-R 3 表示重复启动 3 次。

会得到以下信息

1
2
3
4
5
6
7
8
Stopping: com.antfortune.wealth
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.antfortune.wealth/com.alipay.mobile.quinox.LauncherActivity }
Status: ok
LaunchState: COLD
Activity: com.antfortune.wealth/com.alipay.mobile.quinox.LauncherActivity
TotalTime: 463
WaitTime: 467
Complete

上述 TotalTime 就是整个冷启动的耗时,与 Locat 过滤 Displayed 得到的时间基本是一致的。

这两种方式,统计的都是 App 启动到 Activity 首次调用的时间 onWindowFocusChanged 的时间

如果想统计从 App 启动到数据请求成功后某个布局完全展示出来的耗时,可以在启动终点调用 Activity#reportFullyDrawn 来通知当前

已经完全绘制完成,然后在 Logcat 里过滤 Fully drawn 就可以看到整个流程的耗时。

,