1. Activity启动耗时简介:什么是启动耗时,为何需要埋点
大家好,我是老张。今天开始咱们聊聊Activity启动耗时的埋点。说句实话,这玩意儿我做了好几年,踩过的坑能写一本书。你想想看,一个App启动慢半秒,用户可能就直接划走了。所以这节课,咱们先把基础打牢。
什么是启动耗时?
启动耗时,说白了就是从用户点击图标那一刻,到App首帧完全展示、用户能正常操作的那段时间。注意,是「用户能交互」才算完,光看到闪屏不算。
我当初刚做性能优化时,以为只要Activity.onCreate()执行完就算启动结束。后来被产品怼了一脸——屏幕还在那转菊花,用户点不动,你跟我说启动完了?嗯,从那以后我就记住了:启动结束的标志是「首帧绘制完成 + 用户可点击」。
启动时长的三个类型
咱们行业里一般把启动分成三类,每一类的耗时组成完全不同。我整理了一个表格,方便你对照着看。
| 类型 | 触发场景 | 进程状态 | 典型耗时 |
|---|---|---|---|
| 冷启动 | 进程首次创建 | 无进程 | 1~3秒(正常App) |
| 温启动 | 进程存活,Activity被销毁 | 后台存活 | 200~600ms |
| 热启动 | 进程存活,Activity仍在栈中 | 完全活跃 | 100~300ms |
冷启动是最重的,也是咱们埋点重点关注的。温启动次之。热启动嘛,如果超过500ms,那就有问题了。
记住一点:冷启动的耗时大头在Application.onCreate()和ContentProvider初始化。我建议埋点时就盯着这两块,基本能抓住80%的问题。
为什么需要埋点?
你可能觉得,「我开发时测一下启动时间不就行了?」——太天真了。真实用户的环境千奇百怪:低端机、后台一堆App、网络慢、文件读写卡……你开发机跑得飞快,一上线就崩。
我个人习惯是:没有埋点的性能优化都是瞎搞。为什么?因为你需要数据说话。
- 量化问题:启动到底慢不慢?慢了多少?哪个阶段最慢?
- 监控波动:版本发布后,启动时间有没有变差?
- 定位根因:比如突然慢了500ms,是某个第三方SDK升级导致的,还是自家代码改坏了?
- 验证优化效果:改了代码,启动快了多少?老板要报表,你得拿出数据。
我之前在一个电商项目里,就是靠埋点发现某个启动阶段在低端机上耗时暴增。查了半天,原来是SharedPreferences里存了个巨大的JSON字符串。嗯,这种坑只有线上埋点才能抓到。
启动耗时的组成
为了搞清楚埋点埋在哪,你得先知道启动过程中发生了什么。我画了一张流程图,把关键阶段标出来了。
从图上你能看到,启动其实是个串行流水线。每个阶段都可能成为瓶颈。我建议你重点关注这几个地方:
- Application.onCreate()—— 第三方SDK初始化别放这,能懒加载就懒加载。
- ContentProvider#onCreate()—— 坑中之坑,每个ContentProvider都延后启动时间。
- Activity.onCreate()中的setContentView和findViewById—— 布局层级越深,inflate越慢。
- 首帧绘制时间—— 只有这个才是用户真正看到的东西。
避坑指南:埋点之前别踩的雷
我曾经在一个项目里直接在主线程打印Log来记启动耗时,结果Log本身拖慢了启动。后来一测,Log写入I/O花了200ms。记住:埋点代码本身不能成为性能负担。
另外,启动耗时受太多因素影响。你知道同一台手机,连着充电器和拔掉电源,启动时间能差30%吗?还有系统版本、CPU降频、磁盘碎片……所以埋点时一定要去噪。
- 不要只看单次数据,要统计P50、P95、P99。
- 区分冷/温/热启动,分别上报。
- 记录设备信息(型号、系统、存储剩余)。
小结:你到底需要埋哪些点?
如果你现在要开始做一个启动埋点,我的建议是这样的:
核心打点位置(最少必须埋):
- t0:Application.attachBaseContext() 开始 —— 进程创建时间
- t1:Application.onCreate() 结束 —— 应用初始化完成
- t2:Activity.onCreate() 开始 —— Activity创建
- t3:Activity.onResume() 结束 —— 生命周期完成
- t4:首帧绘制回调(ViewTreeObserver.OnDrawListener 或 ReportFullyDrawn)
这样你就能算出:
进程创建耗时 = t1 - t0
Activity创建耗时 = t3 - t2
首帧绘制耗时 = t4 - t3
总启动耗时 = t4 - t0
为什么没有把用户点击那一刻作为t0?因为从点击到进程创建这段时间,你是拿不到的(系统控制的)。所以t0实际上是进程创建开始,这个大家统一就行。