Now In Android 精讲 9 - Benchmark 与 Baseline Profile

写在前面

Jetpack Compose 应用在冷启动、导航首帧、滚动列表等场景中会受 JIT/AOT 状态影响------早期帧掉得越多,用户体感越差。本文带你了解 Now in Android 项目的 benchmarks 模块,聊聊 Baseline Profile 的原理、如何编写 Macrobenchmark,以及实际收益。


一、为什么要关心 Benchmark 与 Baseline Profile?

痛点 解法
冷启动慢、首帧卡顿 Baseline Profile 在安装期预编译关键路径
"感觉快了"没有数据支撑 Macrobenchmark 量化启动耗时、帧稳定性
JIT 暖机阶段体验差 预编译减少运行时 JIT 抖动

二、Baseline Profile 原理

2.1 工作流程

复制代码
生成 Profile ──▶ 合入 app 模块 ──▶ 安装期注入 ART ──▶ 部分 AOT 预编译
  1. Profile Installer (prod/release 构建默认启用)会在 app 安装阶段把 baseline-prof.txt 注入 ART Profile。
  2. ART 根据 Profile 对命中的类/方法做 Partial AOT,减少运行时 JIT 开销。
  3. 生成 Profile 依赖 Macrobenchmark 跑真机/模拟器场景,提取热点方法签名。

2.2 为什么能让 Compose 冷启动变顺?

  • 安装期预编译:关键路径提前优化,冷启动和首帧更稳。
  • 数据来源:来自真实或模拟的 CUJ(Critical User Journey),覆盖启动 + 渲染 + 交互。
  • 运行时收益:减少"前几秒越来越顺"的暖机阶段;首屏列表滚动更稳定;过渡动画掉帧更少。

三、ForYouBaselineProfile 示例拆解

代码位于 benchmarks/.../baselineprofile/ForYouBaselineProfile.kt

34:43:benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/ForYouBaselineProfile.kt 复制代码
@Test
fun generate() =
    baselineProfileRule.collect(PACKAGE_NAME, maxIterations = 10) {
        startActivityAndAllowNotifications()
        forYouWaitForContent()
        forYouSelectTopics(true)
        forYouScrollFeedDownUp()
    }
要素 说明
BaselineProfileRule.collect 启动待测 app、执行 CUJ、收集方法调用频率
maxIterations = 10 最多跑 10 轮,收敛后提前结束
CUJ 设计 启动 → 等待内容 → 选话题 → 滚动,覆盖核心热点

小技巧:maxIterations 怎么选?

  • UI 稳定时:3--5 次即可,加快生成速度。
  • 复杂场景:可设 10,但注意设备发热导致指标漂移。
  • 多场景拆分:启动、滚动、详情页分开收集,合并到主 Profile,便于定位回归。

四、ForYouActions:CUJ 动作封装

ForYouActions.kt 封装了 For You 场景的 UIAutomator 操作,保证 CUJ 可重复、可读。

4.1 等待内容加载

28:36:benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt 复制代码
fun MacrobenchmarkScope.forYouWaitForContent() {
    device.wait(Until.gone(By.res("loadingWheel")), 5_000)
    val obj = device.waitAndFindObject(By.res("forYou:topicSelection"), 10_000)
    obj.wait(untilHasChildren(), 60_000)
}
  • 二次确认:先等加载圈消失,再等列表有子项。
  • 长超时:最多 60s,降低网络抖动导致的失败。

4.2 选择话题

42:90:benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt 复制代码
fun MacrobenchmarkScope.forYouSelectTopics(recheckTopicsIfChecked: Boolean = false) {
    val topics = device.findObject(By.res("forYou:topicSelection"))
    val horizontalMargin = 10 * topics.visibleBounds.width() / 100
    topics.setGestureMargins(horizontalMargin, 0, horizontalMargin, 0)
    // ... 循环选择至少 3 个话题
}
技巧 说明
setGestureMargins 规避系统边缘手势干扰
recheckTopicsIfChecked 多轮迭代时"点两次"刷新状态
childCount == 0 直接 fail 避免生成空 Profile

4.3 滚动列表

93:96:benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt 复制代码
fun MacrobenchmarkScope.forYouScrollFeedDownUp() {
    val feedList = device.findObject(By.res("forYou:feed"))
    device.flingElementDownUp(feedList)
}

快速下滑再上滑,触发列表布局/绘制热点路径。

4.4 主题切换(可选)

98:108:benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt 复制代码
fun MacrobenchmarkScope.setAppTheme(isDark: Boolean) {
    when (isDark) {
        true -> device.findObject(By.text("Dark")).click()
        false -> device.findObject(By.text("Light")).click()
    }
    device.waitForIdle()
    device.findObject(By.text("OK")).click()
    waitForObjectOnTopAppBar(By.text("Now in Android"))
}

覆盖不同主题下的布局代码路径。


五、StartupBenchmark:量化冷启动

StartupBenchmark.kt 衡量冷启动耗时,对比不同编译/Baseline Profile 状态下的收益。

5.1 四种编译模式

45:57:benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/startup/StartupBenchmark.kt 复制代码
@Test fun startupWithoutPreCompilation() = startup(CompilationMode.None())

@Test fun startupWithPartialCompilationAndDisabledBaselineProfile() =
    startup(CompilationMode.Partial(baselineProfileMode = Disable, warmupIterations = 1))

@Test fun startupPrecompiledWithBaselineProfile() =
    startup(CompilationMode.Partial(baselineProfileMode = Require))

@Test fun startupFullyPrecompiled() = startup(CompilationMode.Full())
模式 说明 用途
None 纯 JIT,冷启动最慢 基线参照
Partial + Disable 部分 AOT,禁用 Baseline 隔离 Profile 差异
Partial + Require 部分 AOT + Baseline 真实发布态
Full 全量 AOT 极限参考

5.2 测量逻辑

59:74:benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/startup/StartupBenchmark.kt 复制代码
private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated(
    packageName = PACKAGE_NAME,
    metrics = BaselineProfileMetrics.allMetrics,
    compilationMode = compilationMode,
    iterations = 5,
    startupMode = COLD,
    setupBlock = {
        pressHome()
        allowNotifications()
    },
) {
    startActivityAndAllowNotifications()
    forYouWaitForContent()
}
参数 说明
iterations = 5 同一配置跑 5 次,提升统计可靠度
startupMode = COLD 冷启动,进程完全退出后重启
setupBlock 每轮前退到桌面、处理通知权限
结束条件 forYouWaitForContent() 等待内容就绪

六、实施步骤

bash 复制代码
┌─────────────────────────────────────────────────────────────┐
│  1. 准备环境                                                 │
│     Android Studio 最新版 + AGP 8+                          │
│     确保 Profile Installer 启用(默认 on)                   │
├─────────────────────────────────────────────────────────────┤
│  2. 生成 Baseline Profile                                    │
│     ./gradlew :benchmarks:pixel6api31aospBenchmark          │
│     产物位于 benchmark/build/outputs/baseline-prof.txt      │
├─────────────────────────────────────────────────────────────┤
│  3. 合入 app 模块                                            │
│     拷贝到 app/src/main/baseline-prof.txt                   │
├─────────────────────────────────────────────────────────────┤
│  4. 验证收益                                                 │
│     再跑 StartupBenchmark,对比各模式指标                    │
├─────────────────────────────────────────────────────────────┤
│  5. CI 集成                                                  │
│     放入可选 Job,大改动后跑一轮防回归                        │
└─────────────────────────────────────────────────────────────┘

七、收益预期

指标 优化前 优化后
冷启动 ~700--900ms ~400--600ms
首帧稳定 明显掉帧 更平滑
滚动流畅度 暖机阶段卡顿 首滚即顺

具体数值因设备/构建差异会有浮动,但趋势一致。


八、维护小技巧

  • 场景可重复:固定账号/数据,必要时禁用动画。
  • 迭代次数适中:冷启动 5--8 次可得稳定均值。
  • 分场景拆分:启动、滚动、详情页分测试,便于定位回归。
  • 定期再生成:功能演进会改变热点路径,Profile 需随版本更新。
  • 设备一致性:优先同型号真机。

结语

Baseline Profile + Macrobenchmark 是 Now in Android 这类 Compose 项目里性价比极高的性能优化手段:

  • 把"安装即顺畅"交付给用户
  • 把"可量化的性能"交给团队

快把 Benchmark 模块跑起来,看看你的启动时间能省下多少毫秒吧!

相关推荐
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android