2、Baseline Profile的编译流程概述

搞 Baseline Profile 之前,得先弄明白它的编译流程到底干了啥。说白了,就是把那些你希望提前编译好的热点代码,塞进 APK 里,让 ART 虚拟机在安装时或首次启动时就把它转成机器码。

我自己最早接触这块时,以为就是搞个配置文件扔进去就行。结果踩了不少坑。后来才理解,整个流程其实分三大阶段:生成、打包、生效。咱们一个一个说。

2.1 整体流程太长不看版

我先画个图,你瞟一眼大概就知道怎么回事了:

Baseline Profile编译流程总览 阶段一:生成(开发机/CI) 运行Macrobenchmark BaselineProfileRule 输出baseline-prof 阶段二:打包(AGP) 合并多个prof文件 编译为baseline.prof 打包进APK assets/ 阶段三:生效(设备端) 安装时由installd解析 ART编译dex2oat 生成odex + 加速启动 提示:整个流程是线性的,任何一个阶段出错都会导致Profile不生效

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 会自动帮你做两件事:

  1. 合并:如果你有多个变体(debug、release)、多个模块,AGP 会把所有 baseline-prof 合并成一个。
  2. 编译:把文本格式转成二进制 baseline.prof 格式,体积更小、解析更快。
  3. 打包:放入 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 从哪来、怎么打包、如何生效,后面遇到编译错误排查时你心里就有底了。