Android 启动优化(二):TTID、TTFD 与 Macrobenchmark 启动性能测量

序、众所周知,每一次开始写这种框架系类的文章的时候,就是我在找工作了,有闲暇时间可以写点东西。哈哈哈哈,26年找工作,简直就是。。。。。

重要的事情说三遍

有岗位的请 CALL 我,谢谢大家了。。。。

有岗位的请 CALL 我,谢谢大家了。。。。

有岗位的请 CALL 我,谢谢大家了。。。。

上一篇我们梳理了一次 Android 冷启动的大致过程:

Launcher → Zygote → ActivityThread → Application → Activity → 首帧显示。

知道启动过程之后,下一个问题就是:

我的 App 启动到底慢不慢?

启动优化不能靠"感觉"。

比如我们把一个 SDK 延迟初始化之后,感觉页面好像出来得更快了,但到底快了多少?是快了 20ms,还是 200ms?

如果没有统一的测量方式,优化前后的结果就无法比较。

所以启动优化真正开始之前,需要先建立:

启动性能基线。


一、启动速度到底测什么?

很多人刚接触启动优化时,会把:

Application.onCreate() 耗时

或者:

Activity.onCreate() 耗时

直接当成启动耗时。

这是不准确的。

因为真正的启动过程远不止这两个方法。

例如一次冷启动还包括:

  • 创建 App 进程

  • ClassLoader 初始化

  • ContentProvider 初始化

  • Application 初始化

  • Activity 创建

  • Layout Inflate

  • Measure

  • Layout

  • Draw

  • RenderThread

  • 首帧显示

因此:

单独测一个生命周期方法,不能代表完整启动时间。

真正评价启动体验,Android 中最重要的两个指标是:

TTID

和:

TTFD


二、什么是 TTID?

TTID 全称:

Time To Initial Display

可以理解成:

从用户启动 App,到第一帧真正显示出来需要多长时间。

例如:

用户点击 App:

0ms

系统创建进程

Application 初始化

MainActivity 创建

首页布局绘制

第一帧显示:

340ms

那么这次:

TTID ≈ 340ms

TTID 关注的是:

用户什么时候第一次看到 App 的页面。

所以它非常适合衡量:

启动首屏速度。


三、TTID 不代表页面已经完全可用

这里有一个很容易误解的地方。

假设首页启动过程是这样的:

0ms

用户点击 App。

300ms

Activity 第一帧出来。

页面上已经有:

Logo、标题、底部导航。

800ms

数据库读取完成。

1100ms

首页列表加载完成。

1300ms

页面所有关键功能可以使用。

那么:

TTID 可能只有:

300ms

但用户真正可以正常使用页面,却需要:

1300ms

所以:

首帧快,不代表真正启动体验一定好。

于是就有了另外一个指标:

TTFD。


四、什么是 TTFD?

TTFD 全称:

Time To Full Display

可以理解成:

从启动 App,到页面真正完成关键内容加载的时间。

TTID 关注:

"页面出来了吗?"

TTFD 关注:

"页面真正准备好了吗?"

例如一个新闻 App。

首屏 300ms 出现:

  • Toolbar

  • Tab

  • 白色列表区域

但是新闻数据 900ms 才显示。

那么:

TTID:

大约 300ms

TTFD:

可能接近 900ms

这两个指标关注的是不同阶段。


五、TTID 和 TTFD 怎么理解最简单?

可以简单记成:

TTID

用户什么时候看到东西。

TTFD

用户什么时候真正可以正常使用。

举个更加直观的例子。

一个外卖 App:

点击图标。

300ms:

首页框架出来了。

600ms:

定位完成。

800ms:

商家列表出现。

1000ms:

优惠、推荐等数据全部加载完成。

那么:

TTID 可能是 300ms。

而:

TTFD 可能接近 800~1000ms。

所以一个比较完整的启动优化,不能只盯着:

第一帧。

最终还要关注:

用户真正进入可用状态的时间。


六、为什么不能自己简单打时间戳?

早期我们很容易这么做:

在 Application 开始的位置记一个时间。

然后在 Activity.onResume() 再记一个时间。

两者相减:

这就是启动时间。

这种方式可以辅助分析某段业务代码,但作为完整启动性能指标并不可靠。

因为它没有包含完整的系统启动过程。

比如:

Application 开始计时之前,系统已经做过:

  • 进程创建

  • Runtime 初始化

  • ClassLoader 初始化

  • 一部分 Framework 工作

另外:

Activity.onResume() 被调用,也并不代表:

第一帧已经真正显示到屏幕。

所以自己打点更适合:

分析具体业务阶段耗时。

而完整启动性能测试,更适合使用系统提供的性能测试工具。

现在官方比较推荐的一套方案就是:

Macrobenchmark


七、Macrobenchmark 是什么?

Macrobenchmark 是 Android Jetpack 提供的一套:

应用级性能测试工具。

它和普通 Unit Test 不一样。

Macrobenchmark 可以直接针对一个真实 App 做性能测试,例如:

  • 冷启动

  • 温启动

  • 热启动

  • 页面滚动

  • 页面跳转

  • Frame 性能

  • Baseline Profile 相关性能验证

对于启动优化来说,它最大的作用就是:

建立稳定、可重复的启动性能基线。

我们可以让它自动:

启动 App

记录性能数据

关闭 App

重新启动

重复多次

输出最终结果。

相比自己拿秒表或者打印 Log,要可靠得多。


八、为什么 Benchmark 要单独建一个模块?

Macrobenchmark 通常不会直接写在业务 App 模块里面。

项目结构一般类似:

app

或者我们当前项目里的:

ReDexSample

负责真正运行的 App。

另外建立:

benchmark

模块。

benchmark 模块负责:

从外部控制目标 App,并测量它。

也就是说:

benchmark

启动

ReDexSample

记录启动性能。

这种设计非常重要。

因为我们想测的是:

App 从外部真正启动时的完整性能。

而不是 App 自己启动之后再开始计时。


九、Macrobenchmark 启动测试的核心

一个典型的启动 Benchmark,本质上主要需要告诉系统三件事情:

第一:

测谁?

例如:

com.sample.redex

第二:

测什么?

例如:

StartupTimingMetric

第三:

怎么启动?

例如:

StartupMode.COLD

然后 Macrobenchmark 会多次执行:

停止 App

冷启动 App

记录启动耗时

保存 Trace

再次执行。


十、StartupMode.COLD 是什么?

Macrobenchmark 支持不同启动模式。

最重要的就是:

COLD

冷启动。

App 进程不存在。

需要重新:

  • 创建进程

  • Application 初始化

  • Activity 创建

  • 首帧绘制

这是启动优化最关注的模式。


WARM

温启动。

进程通常仍然存在,但是 Activity 需要重新创建或恢复相应状态。


HOT

热启动。

App 和 Activity 的大部分状态仍然存在。

主要是把页面重新带回前台。


在实际启动优化中:

通常优先分析 Cold Startup。

因为冷启动链路最长,也最容易暴露问题。


十一、StartupTimingMetric 测什么?

Macrobenchmark 做启动测试时,经常会使用:

StartupTimingMetric

它主要帮助我们获取启动相关的时间指标。

其中最常见的就是:

timeToInitialDisplayMs

也就是:

TTID。

如果应用正确上报"页面已经完全准备完成"的时机,还可以进一步观察 Full Display 相关指标。

所以我们后面看到:

timeToInitialDisplayMs

基本可以直接理解为:

从启动到首帧显示耗时。


十二、Iteration 是什么意思?

启动性能存在波动。

同一个 App 连续启动五次,不可能每一次都是:

340.2ms

可能是:

第一次:

352ms

第二次:

325ms

第三次:

387ms

第四次:

340ms

第五次:

329ms

原因很多,例如:

  • CPU 调度

  • 系统后台任务

  • IO

  • 温度

  • 缓存状态

  • GC

  • 设备负载

所以性能测试不能只跑:

一次。

Macrobenchmark 会通过:

Iterations

重复执行测试。

例如:

5 次。

然后输出:

  • min

  • median

  • max

等结果。


十三、为什么我更关注 median?

假设 5 次结果是:

325ms

330ms

340ms

350ms

387ms

其中:

最小值:

325ms

最大值:

387ms

中位数:

340ms

如果只看最小值:

可能过于乐观。

如果只看最大值:

又可能被某次系统抖动影响。

所以实际分析时,我通常更关注:

Median,中位数。

它更适合作为当前版本的性能基线。

当然,真正线上做性能监控时,还会进一步看:

P50、P90、P95 等分位数。


十四、一次真实的 Macrobenchmark 测试

我目前测试的项目是:

Chapter22-master

目标 App 模块:

ReDexSample

Benchmark 模块:

benchmark

测试设备:

小米 M2102K1AC

系统:

Android 14 / API 34

测试的是:

Cold Startup

运行:

5 次 Iteration

最终 Macrobenchmark 输出:

ExampleStartupBenchmark_startup

指标:

timeToInitialDisplayMs

结果:

  • min:324.7ms

  • median:340.2ms

  • max:387.0ms

因此当前可以先建立一个启动性能基线:

TTID median ≈ 340.2ms

这一步非常重要。

因为从现在开始,后面所有启动优化都必须和这个数字比较。


十五、340ms 到底算不算慢?

看到:

340ms

很多人第一反应就是:

这个数字到底好不好?

实际上不能简单脱离场景判断。

启动速度会受到很多因素影响:

  • 设备性能

  • Android 版本

  • App 复杂程度

  • 首页布局

  • SDK 数量

  • 数据加载方式

  • 是否使用 Profile

  • Release / Debug 构建方式

所以性能优化更重要的不是:

和别人比一个绝对数字。

而是:

建立自己的基线,然后持续对比。

例如现在:

优化前:

340.2ms

如果优化 Application 后:

315ms

再优化首页布局:

290ms

再使用 Baseline Profile:

260ms

那么我们就能非常清楚地知道:

每一次优化到底有没有效果。


十六、Macrobenchmark 最大的价值不是"测一个数字"

Macrobenchmark 真正有价值的地方还有一个:

它会生成 System Trace。

例如我们跑 5 次测试以后,会看到:

Iteration 0

Iteration 1

Iteration 2

Iteration 3

Iteration 4

每一次启动都对应一条 Trace。

这些 Trace 可以使用:

Perfetto / System Trace

打开。

然后我们就可以继续往下分析:

340ms 到底花在哪里?

例如:

是:

bindApplication

比较慢?

还是:

Application.onCreate

比较慢?

还是:

Activity.onCreate

比较慢?

还是:

traversal

比较慢?

又或者:

主线程大量:

Runnable

一直抢不到 CPU?

这就把启动优化从:

"感觉慢"

变成了:

"有数据、有 Trace、有证据。"


十七、Macrobenchmark 和 Perfetto 的关系

这两个工具千万不要混淆。

可以简单记成:

Macrobenchmark

回答:

有多慢?

例如:

TTID:

340.2ms


Perfetto

回答:

为什么慢?

例如:

Application:

60ms

Activity:

90ms

Layout:

40ms

IO:

30ms

线程等待:

20ms

......

所以完整流程应该是:

Macrobenchmark

测出:

340ms

Perfetto

分析:

340ms 花在哪里。

这才是真正进入启动优化。


十八、为什么性能测试最好使用真机?

启动性能和:

  • CPU

  • 内存

  • IO

  • 调度

  • 系统版本

都有直接关系。

模拟器的运行环境和真实手机差别非常大。

所以性能优化中一个很重要的原则就是:

尽量使用真机测试。

并且最好固定:

  • 同一台设备

  • 同一个系统版本

  • 同一种构建类型

  • 相同测试场景

否则前后结果可能不具备可比性。

例如:

优化前用旗舰机测试。

优化后换了一台老设备。

那两个数字比较基本没有意义。


十九、启动性能测试还要注意环境稳定

即使使用同一台设备,也需要尽量减少外部干扰。

例如测试过程中:

后台正在下载文件。

或者:

手机正在发热降频。

或者:

系统正在执行大量后台任务。

都会影响结果。

因此一次性能优化最好不要只看:

某一次测试结果。

而是通过多次测试观察:

整体趋势。

例如:

优化前:

Median = 340ms

连续测试基本在:

330~360ms

优化后:

Median = 285ms

连续测试基本在:

275~300ms

这种下降才更有说服力。


二十、不要为了数字而优化

还有一点非常重要。

启动优化不是为了:

把 Benchmark 数字做到最低。

最终还是要回到:

用户体验。

例如一种"优化"方式:

App 300ms 就显示一个空白首页。

所有真正内容:

2 秒以后才加载。

那么 TTID 的确非常漂亮。

但用户体验可能反而更差。

所以真正完整的启动优化,最终应该同时关注:

首帧速度

和:

页面真正可用速度。

也就是:

TTID

TTFD

而不是单纯追求一个更小的首帧数字。


二十一、建立启动优化基线

到这里,我们现在已经有了第一组非常重要的数据:

当前版本:

Cold Startup

设备:

小米 M2102K1AC / Android 14

测试工具:

Macrobenchmark

Iterations:

5

TTID:

  • min:324.7ms

  • median:340.2ms

  • max:387.0ms

因此:

340.2ms 就是我们当前启动优化实验的基准线。

以后每做一次优化:

都应该重新跑 Macrobenchmark。

例如:

业务初始化优化:

重新测。

Baseline Profile:

重新测。

Startup Profile:

重新测。

DEX 类重排:

重新测。

最终用数据判断:

到底有没有真正改善启动性能。


二十二、小结

这一篇主要解决了一个问题:

Android 启动速度到底应该怎么测?

我们先认识了两个核心指标:

TTID

表示:

从启动到第一帧显示。

TTFD

表示:

从启动到页面真正完成关键内容加载。

然后使用:

Macrobenchmark

建立稳定的启动性能基线。

当前实测结果:

TTID median ≈ 340.2ms

但这时候我们仍然只知道:

App 启动需要大约 340ms。

还不知道:

这 340ms 到底花在哪里。

因此下一篇就正式进入启动优化最关键的一步:

Android 启动优化(三):Perfetto 实战------340ms 启动时间到底花在哪里?

下一篇我们会开始真正看:

  • Main Thread

  • bindApplication

  • Application

  • Activity

  • Running

  • Runnable

  • Waiting

  • Wall Duration

  • CPU Duration

  • IO

  • Binder

  • GC

  • Choreographer

  • traversal

从:

"测出来慢"

正式进入:

"找到为什么慢"。

相关推荐
mldong31 分钟前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排1 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60612 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角3 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!3 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰3 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi5 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen5 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马6 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒6 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端