夜雨聆风学习资料网

ARTICLE · 1208042

App 启动慢?Baseline Profile 让冷启动提速 40%

App 启动慢?Baseline Profile 让冷启动提速 40%

你有没有遇到过这种情况——用户打开你的 App,等了 2 秒白屏,然后默默按了返回键。

或者更扎心的:你在开发机上测试启动飞快,一到用户手机上就慢得像蜗牛。你打开 Android Studio Profiler 看了一圈,感觉哪都没问题,但用户投诉就是越来越多。

今天聊的这个技术,可能就是那块缺失的拼图——Baseline Profile。它不需要改业务代码,不需要重构架构,但可能让你的冷启动直接快 30%~40%。

一句话理解:Baseline Profile 就是提前告诉 Android 运行时(ART):"这些代码是启动关键路径,请提前编译好,别等用户点了图标才开始 JIT 编译。"

———— 一 ————

1. ART 的"暖场"问题

Android Runtime(ART)执行代码并不是一开始就全速运转的。当你刚装或刚更新了 App,ART 对你的代码一无所知:

▸ 解释执行:最初所有方法都走解释器,速度最慢

▸ JIT 编译:运行过程中,ART 发现某些方法被频繁调用(热点),才触发即时编译

▸ AOT 编译:空闲时根据收集到的热点信息,在后台做全量编译优化

问题来了——冷启动恰恰发生在"ART 还不认识你的代码"的时候。

Application.onCreate()、首页 Activity 创建、首屏布局 inflate、各种 SDK 初始化……这些关键路径上的代码,此刻全都在解释执行或等待 JIT。用户感受到的卡顿,很多时候不是因为代码写得烂,而是因为编译优化还没到位。

这就好比一家餐厅刚开张,厨房还没热好锅,前几个客人的菜就是慢——不是厨师手艺不行,是"暖场"还没完成。

2. Baseline Profile 解决了什么

Baseline Profile 的思路很简单:

既然 ART 需要运行一段时间才知道哪些代码是热点,那我们就提前告诉它。

你把一份"关键代码路径清单"打包进 APK,用户安装 App 时,ART 会发现这份清单,然后在后台启动一个轻量级的编译过程,把清单上的方法提前编译成机器码。等用户真正点开 App 的时候,关键路径上的代码已经是优化过的状态了。

整个过程:

打包阶段:Profile 文件放入 APK 的 assets/dexopt/baseline-prof 目录
安装阶段:ART 发现 Profile → 后台预编译清单中的关键方法
启动阶段:关键路径代码直接以编译后状态执行 → 启动提速

Baseline Profile 能优化的场景不止冷启动,还包括:

▸ 首页渲染与列表滚动

▸ 高频页面跳转

▸ Compose 首次组合

▸ 登录流程等核心业务路径

3. 先看数据:到底能快多少

空口无凭,以下是一个真实项目的 Macrobenchmark 冷启动测试结果(同一设备、同一构建、10 次迭代取中位数):

优化前

340ms

TTID median

→

优化后

295ms

TTID median

从 340ms 降到 295ms,减少 45ms,提升约 13%。这个数据看起来不算夸张?别忘了——我们一行业务代码都没改。

如果是那些还没做过启动优化、Application 里堆满 SDK 初始化的项目,Baseline Profile 的收益会更显著,冷启动提速 30%~40% 完全可能。

———— 二 ————

4. 第一步:创建 Macrobenchmark 模块

Baseline Profile 的生成依赖 Jetpack Macrobenchmark 框架。先在项目中新建一个 benchmark 模块:

项目结构:

app/ benchmark/   src/main/AndroidManifest.xml   src/main/java/.../StartupBenchmark.kt

benchmark 模块的 build.gradle.kts:

plugins {     id("com.android.test")     id("org.jetbrains.kotlin.android") }  android {     namespace = "com.example.app.benchmark"     targetProjectPath = ":app"      defaultConfig {         minSdk = 23         testInstrumentationRunner =             "androidx.test.runner.AndroidJUnitRunner"     } }  dependencies {     implementation("androidx.test.ext:junit:1.2.1")     implementation("androidx.test.uiautomator:uiautomator:2.3.0")     implementation("androidx.benchmark:benchmark-macro-junit4:1.3.4") }
⚠ 注意
目标 App 需要提供 profileable 能力。通常通过专用构建类型配置,不要为了跑测试把 release 包改成 debuggable。

5. 第二步:编写冷启动基准测试

先写一个最小的冷启动 Macrobenchmark,用来建立优化前的基线数据:

@RunWith(AndroidJUnit4::class) class StartupBenchmark {      @get:Rule     val benchmarkRule = MacrobenchmarkRule()      @Test     fun coldStartup() = benchmarkRule.measureRepeated(         packageName = "com.example.app",         metrics = listOf(StartupTimingMetric()),         compilationMode = CompilationMode.Partial(             baselineProfileMode = BaselineProfileMode.Require         ),         startupMode = StartupMode.COLD,         iterations = 10,         setupBlock = {             pressHome()         }     ) {         startActivityAndWait()     } }

几个关键参数解释:

▸ StartupMode.COLD:保证每轮从无进程状态启动,模拟真实冷启动

▸ iterations = 10:跑 10 次取统计值,不能只看最好的一次

▸ CompilationMode.Partial(...):指定编译模式,后面验证时会用到

运行这个测试,你会得到类似这样的结果:

// 优化前基线 TTID min:    324.7ms TTID median: 340.2ms TTID max:    387.0ms
记住这个 340.2ms——它是后面所有优化实验的对照组。没有这个数字,你就无法证明 Baseline Profile 到底有没有效果。

6. 第三步:生成 Baseline Profile

现在用 BaselineProfileRule 编写一个生成器,让自动化脚本模拟真实用户操作:

@RunWith(AndroidJUnit4::class) class BaselineProfileGenerator {      @get:Rule     val rule = BaselineProfileRule()      @Test     fun generate() = rule.collect(         packageName = "com.example.app",         includeInStartupProfile = true     ) {         pressHome()         startActivityAndWait()          // 模拟真实用户高频路径         device.findObject(By.text("推荐")).click()         device.waitForIdle()          device.findObject(By.res("com.example.app", "feed_list"))             .setGestureMargin(device.displayWidth / 5)             .fling(Direction.DOWN)     } }

运行后,生成的 Profile 文件会出现在:

app/src/release/generated/baselineProfiles/   ├── baseline-prof.txt    // ~2万行方法清单   └── startup-prof.txt     // 启动类重排信息
⚠ 关键提醒
生成场景要稳定且覆盖真实高频路径。依赖文案定位控件(如 By.text("推荐"))容易因国际化或产品改版失效,优先使用稳定的资源 ID。

7. 第四步:集成到 App 构建

在 app 模块的 build.gradle.kts 中添加 Baseline Profile 插件:

plugins {     id("com.android.application")     id("org.jetbrains.kotlin.android")     id("androidx.baselineprofile") }  android {     // ... 其他配置     buildTypes {         release {             // Baseline Profile 会自动合并到 release 构建中         }     } }  dependencies {     // 引入 benchmark 模块生成的 profile     implementation("androidx.profileinstaller:profileinstaller:1.4.1")     "baselineProfile"(project(":benchmark")) }

———— 三 ————

8. 最重要的一步:对照实验

很多人加完 Baseline Profile,看到 Build Success 就宣布优化成功了。这是最大的误区。

✘ 反面教材
加上 Baseline Profile → Build Success → 宣布启动优化成功。
实际上 Profile 可能根本没打进 APK,或者场景没覆盖启动关键路径,效果为零。

正确做法是单变量对照实验——在完全相同的条件下,分别跑两组测试:

对照组 A:不使用 Baseline Profile

compilationMode = CompilationMode.None()  // 结果:TTID median = 340ms

对照组 B:使用 Baseline Profile

compilationMode = CompilationMode.Partial(     baselineProfileMode = BaselineProfileMode.Require )  // 结果:TTID median = 295ms

条件保持一致:同一台设备、同样构建类型、同样测试次数。如果多次测试结果稳定,才可以判断 Baseline Profile 对当前项目确实有收益。

⚠ 如果结果没变化?
常见原因:
1. Profile 没有打进 release 构建——检查构建产物
2. 自动化场景没有覆盖启动关键路径——补充生成场景
3. 瓶颈主要是主线程 I/O、锁等待或网络——Profile 解决不了这些,要先做业务层优化
4. 测试设备状态不稳定——换设备或增加迭代次数

———— 四 ————

9. 别搞混了:两个 Profile 的区别

这俩名字太像,搞混的人不在少数。一句话区分:

Baseline Profile
Startup Profile
管什么
代码怎么执行
启动代码怎么排列
作用于
ART 运行时编译优化
R8/D8 的 DEX 布局优化
效果
关键方法提前编译
启动类在 DEX 中靠前排列
记忆口诀
管执行
管布局

两者机制不同但最终都可能改善启动性能。测试时也不要一次性全加上——否则 340ms → 285ms 的结果很漂亮,但你完全不知道是谁产生的收益。

———— 五 ————

10. 在 CI 中防止性能回退

启动优化最怕的不是没做,而是做完之后又慢慢退回去。

业务持续增长——新增 SDK、加一个 ContentProvider、多一个数据库查询、首页又多了两个模块。每次可能只增加 10~20ms,单独看不明显,半年下来启动就从 350ms 悄悄涨回 500ms。

这就是性能劣化,也是为什么启动优化不能只做一次。

理想的方案是在 CI 中接入 Macrobenchmark 作为性能回归测试:

Measure 测量→Analyze 定位→Optimize 优化→Verify 验证→Monitor 监控

CI 需要保存的产物:

▸ Macrobenchmark 的 JSON 结果(含 TTID/TTFD 中位数与高分位)

▸ 对应构建版本与提交信息

▸ 失败或异常样本的 Trace 文件

▸ 历史中位数和高分位趋势

门槛不要只写一个绝对毫秒值——不同设备差异很大。更实用的方式是固定测试设备,结合相对回退比例和最小变化量判断。例如:只有当中位数明显变慢且超过噪声区间时,才阻止合并。

线上也要看:实验室数据 ≠ 线上数据。开发者用旗舰机测试很快,但真实用户可能在用旧手机、小内存、慢闪存。线上监控重点关注 P50 / P90 / P95,尤其是低端机的分位数据——很多性能问题在旗舰机上根本看不出来。

———— 六 ————

11. 一张图记住全部

整个启动优化的方法论,最后就五个词:

先测量→找瓶颈→做优化→重验证→防回退

Baseline Profile 是"做优化"这一环里的重要武器,但它不是全部。正确的顺序是:

① 先解决业务层大头——延迟初始化、删除无用 SDK、按需加载

② 再用 Baseline Profile——让关键路径代码提前编译

③ 最后考虑深层优化——Startup Profile、DEX Layout、ReDex InterDex

而每一步,都要用 Macrobenchmark 数据说话。不靠感觉,不挑最好的一次结果,看中位数、看高分位、看趋势。

性能优化最终的 KPI 只有一个:用户到底更快看到页面了吗?用户到底更快可以正常使用 App 了吗?

工具只是手段,性能闭环才是方法。

相关学习资料