启动时间测量方法论:ADB、Systrace、自定义埋点

做性能优化,第一件事不是动手改代码。

而是搞清楚——你现在到底有多慢。

说白了,测量是优化的前提。没有数据,你连问题在哪都不知道。

我在早期做动态化项目时,就踩过这个坑。埋头优化了半天,结果发现测的数据根本不准,白忙活一场。从那时起,我养成了一个习惯:先建好度量体系,再动手优化。

这一章,我把最常用的三种启动时间测量方法整理出来。它们各有侧重,适合不同场景。

1. ADB 命令:最快拿到冷启动耗时

ADB(Android Debug Bridge)是安卓开发者最基础的调试工具。启动时间测量,用它就够了。

我常用的命令是这样的:

adb shell am start -W -n com.example.app/.MainActivity

执行后,终端会返回类似以下的信息:

Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 350
TotalTime: 350
WaitTime: 365

几个关键字段说明一下:

  • ThisTime:当前 Activity 的启动耗时(不含上一个 Activity 的销毁时间)
  • TotalTime:整个启动链路的耗时,包含 Application 的 onCreate 等阶段
  • WaitTime:从用户点击到界面可交互的完整时间

我个人更关注 TotalTime。它反映了从进程创建到页面首帧渲染的全过程。

小技巧: 建议每次测量前,先强制停止应用,消除热启动影响。
adb shell am force-stop com.example.app

2. Systrace 与 Perfetto:找出瓶颈在哪

ADB 只能告诉你「多慢」。但如果你想搞清楚「为什么慢」,就得用 Systrace 或 Perfetto 了。

Systrace 是 Android 内置的 trace 工具。它能把系统层面、应用层面的耗时操作,按时间轴展开给你看。我现在更推荐用 Perfetto,它是 Systrace 的升级版,功能更强,UI 也更好用。

使用方法很简单:

# 启动 Perfetto 录制(需配置 trace 点)
python3 /path/to/perfetto/tools/record_android_trace \
    -c /path/to/config.pbtx \
    -o /path/to/output.trace

我个人习惯在应用启动时,加入以下自定义 trace 段:

Trace.beginSection("App.onCreate");
// 初始化逻辑
Trace.endSection();

Trace.beginSection("DynamicEngine.init");
// 引擎初始化
Trace.endSection();

在 Perfetto 的 UI 上,你能看到这些 trace 段的开始、结束、以及内部的子任务。有一次我就通过这种方式,发现某个 SDK 的初始化竟然占了 800ms,而我之前一直以为是网络加载慢。

注意: Perfetto 录制不要在生产环境一直开着,会有性能开销。建议在测试机上,开启飞行模式后录制,避免网络请求干扰。

3. 自定义埋点:精确到毫秒级的核心链路

ADB 和 Perfetto 都有一个问题:它们测的是「系统视角」的启动时间。但业务方关心的,往往是「用户视角」的时间——比如从点击图标到看到首页内容。

这时候,就需要在代码里埋点。

我的做法是,定义几个关键的里程碑:

  1. Application.onCreate 开始
  2. 动态化引擎初始化完成
  3. 首页请求发送
  4. 首页请求返回
  5. 首屏渲染完成

代码实现大致如下:

class StartupMonitor {
    companion object {
        private val milestones = mutableMapOf<String, Long>()

        fun mark(name: String) {
            milestones[name] = SystemClock.elapsedRealtime()
            Log.d("StartupMonitor", "[$name] at ${milestones[name]}")
        }

        fun report() {
            val sorted = milestones.entries.sortedBy { it.value }
            sorted.forEachIndexed { index, entry ->
                if (index > 0) {
                    val diff = entry.value - sorted[index - 1].value
                    Log.d("StartupMonitor", "${entry.key} took ${diff}ms")
                }
            }
        }
    }
}

然后在启动流程的各个节点调用 StartupMonitor.mark("xxx")。

我最推崇这种做法,因为它精确、可聚合、可对比。线上环境也能用,只要做好开关控制。

核心原则: 三种方法不是互斥的,而是互补的。

  • 本地调试用 ADB 快速验证
  • 排查性能瓶颈用 Perfetto
  • 线上监控和对比用 自定义埋点
启动时间测量方法论 启动时间测量方法论 ADB 命令 Systrace / Perfetto 自定义埋点 适用场景 本地快速验证冷启动时间 获取精确到毫秒的耗时值 适用场景 定位性能瓶颈(耗时在哪) 分析系统级/应用级 trace 适用场景 线上用户视角的精确测量 聚合统计与版本对比 三者互补,覆盖全场景 本地调试 + 瓶颈分析 + 线上监控 核心思路:先量化,再优化。没有准确测量,所有优化都是盲目的。

好了,这三种方法就介绍完了。ADB 用来快速摸底,Perfetto 用来追查根因,自定义埋点用来做长期监控。三者结合使用,你才能对启动时间有一个完整、准确的认知。

嗯,这里要再强调一句:测量本身也要注意精度。比如多次测量取中位数、排除异常值、保证测试环境一致。这些东西看起来很基础,但很多人都会忽略。