2、Baseline Profile的编译流程概述
搞 Baseline Profile 之前,得先弄明白它的编译流程到底干了啥。说白了,就是把那些你希望提前编译好的热点代码,塞进 APK 里,让 ART 虚拟机在安装时或首次启动时就把它转成机器码。
我自己最早接触这块时,以为就是搞个配置文件扔进去就行。结果踩了不少坑。后来才理解,整个流程其实分三大阶段:生成、打包、生效。咱们一个一个说。
2.1 整体流程太长不看版
我先画个图,你瞟一眼大概就知道怎么回事了:
2.2 生成阶段——Profile 数据从哪来
这一步发生在你的开发机上,或者 CI 机器上。我们用 Jetpack Macrobenchmark 库写个测试,模拟用户最常用的操作——比如冷启动主界面、滑动列表、打开二级页面。
核心文件:BaselineProfileRule 这个 Rule 会在测试跑完后,自动帮你收集方法调用栈,输出一个 baseline-prof 的文本文件。
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule
val baselineProfileRule = BaselineProfileRule()
@Test
fun generateBaselineProfile() {
baselineProfileRule.collect(
packageName = "com.example.app",
profileBlock = {
startActivityAndWait()
// 模拟实际用户操作
device.wait(Until.hasObject(By.text("首页")), 5_000)
device.click(device.findObject(By.text("我的")))
// ...更多关键路径操作
}
)
}
}
我个人习惯:别只覆盖冷启动。把最常用的 3~5 个高频页面都跑一遍。这样 Profile 能覆盖到登陆后、列表加载、图片显示等核心路径。
生成的 baseline-prof 文件长这样:
HSPLcom/example/app/MainActivity;->onCreate(Landroid/os/Bundle;)V
HSPLcom/example/app/MainActivity;->onResume()V
HSPLcom/example/app/data/Repository;->loadData()Ljava/util/List;
每个条目代表一个方法,前缀表示编译策略:HSP 表示 Hot Startup(热点启动方法),SP 表示 Startup(启动方法),W 表示 Warm(暖启动)。
2.3 打包阶段——AGP 怎么把它塞进去
我记得 Android Gradle Plugin 从 7.0 开始就内置了对 Baseline Profile 的支持。你只需要把 baseline-prof 文件放到 src/main/baselineProfiles/ 目录下,AGP 会自动帮你做两件事:
- 合并:如果你有多个变体(debug、release)、多个模块,AGP 会把所有
baseline-prof合并成一个。 - 编译:把文本格式转成二进制
baseline.prof格式,体积更小、解析更快。 - 打包:放入 APK 的
assets/dexopt/baseline.prof路径下。
你可以在 build/outputs 里找到合并后的中间产物,方便排查。
我曾经犯过一个低级错误:把 baseline-prof 文件手动放到了 src/main/assets/ 下。结果 AGP 根本没识别到,Profile 也没被打进去。记住,必须是 baselineProfiles/ 目录,不是 assets/。
2.4 生效阶段——ART 如何用 Profile 编译
这一步发生在用户设备上。当 APK 被安装时(或首次启动时),系统服务 installd 会读取 APK 里的 baseline.prof 文件。接着 ART 的 dex2oat 编译器开工了:
- 读取 Profile 列表:知道哪些类和方法是热点。
- 编译为机器码:把这些方法提前编译成 odex 文件(优化的 dex)。
- 放入 dalvik-cache:下次启动直接加载机器码,省去解释执行的开销。
为什么这样就能加速?你想想看,没有 Profile 时,ART 只能按需编译,用户第一次点击某个页面,虚拟机还得先解释执行,热起来后再 JIT 编译。有了 Baseline Profile,这些方法在安装时就变成了机器码,用户打开就是满速运行。
2.5 验证流程是否正确走通
到这一步,你可能会问:我怎么知道 Profile 确实生效了?这里给你一个检查清单:
| 验证环节 | 方法 | 期望结果 |
|---|---|---|
| APK 内是否包含 Profile | unzip -l app-release.apk | grep baseline.prof |
输出 assets/dexopt/baseline.prof |
| Profile 内容是否正确 | apkanalyzer prof --size app-release.apk |
显示方法数量 > 0 |
| 设备端是否生效 | adb shell dumpsys package dexopt | grep 你的包名 | 字段 profileStatus 为 baseline |
核心结论:Baseline Profile 本质是一个“提前编译的指令列表”。它不改变应用逻辑,只改变 ART 的编译时机。生成阶段决定了覆盖哪些方法,打包阶段决定是否能正确塞入,生效阶段决定了安装后能否被 dex2oat 利用。
2.6 常见勘误点(提前避坑)
我遇到过不少团队,写着写着就把 Profile 玩坏了。这里列几个高频雷区:
- Profile 文件格式错误:多了一个空行,或者方法签名写错。ART 解析时会直接忽略整条 Profile,导致无效。
- 覆盖的方法太少:比如只覆盖了 Activity.onCreate,没有覆盖依赖库里的代码。膨胀的启动时间依然没优化。
- Profile 版本与编译工具不匹配:老版本的 AGP 生成旧格式 Profile,新 ART 不兼容,安装时 fallback 到默认。
- 多模块工程下漏了模块:每个功能模块都要单独跑 Benchmark 生成自己的 Profile。AGP 合并时才会完整。
嗯,这一节我们把编译流程过了一遍。搞清楚了 Profile 从哪来、怎么打包、如何生效,后面遇到编译错误排查时你心里就有底了。