首页
/ Now in Android 性能基准测试与 Baseline Profile 实战:深入解析 `:benchmarks` 模块

Now in Android 性能基准测试与 Baseline Profile 实战:深入解析 `:benchmarks` 模块

2026-09-12 11:52:07作者:彭桢灵Jeremy

导读

本文以 Now in Android(NIA)仓库中的 :benchmarks 模块为核心,系统讲解如何在一个采用 Kotlin + Jetpack Compose 的多模块 Android 应用中建立"性能基准测试 + Baseline Profile 自动生成"的完整闭环。你将掌握 Macrobenchmark 的四种编译模式对比、启动耗时/帧率/功耗三类指标的采集方法、Baseline Profile 生成与 Dex Layout Optimization(Dex 布局优化)的落地方式,以及如何用 UiAutomator 封装可复用的用户旅程(Critical User Journey,CUJ)测试动作。仓库内所有源码证据均可在 benchmarks/ 目录下找到。

:benchmarks 模块的定位与依赖关系

settings.gradle.kts 中,:benchmarks 被显式纳入构建:include(":benchmarks")。它是整个仓库中唯一的 android-test 类型模块(benchmarks/build.gradle.kts 同时应用了 androidx.baselineprofilenowinandroid.android.test 两个插件),承担两类职责:

  1. Macrobenchmark 基准测试:测量应用冷启动时间、列表滚动帧率、状态变更重组开销与功耗;
  2. Baseline Profile 生成:通过脚本化用户旅程,产出发布版 APK 内嵌的 Profile 规则,加速应用启动与关键路径执行。

benchmarks/README.md 给出的模块依赖图完整刻画了它在整个仓库拓扑中的位置。核心关系只有两条边:

  • :app -.->|baselineProfile| :benchmarks:应用模块通过 baselineProfile(projects.benchmarks)(见 app/build.gradle.kts)引入 benchmarks 作为 Profile 生成源;
  • :benchmarks -.->|testedApks| :app:基准测试模块通过 targetProjectPath = ":app" 指定被测应用,构成"测试模块 → 被测 APK"的配对关系。

这与图例中的语义一致:实线箭头(-->)表示编译期硬依赖(如 :core:data --> :core:database),虚线箭头(-.->)表示测试期/运行期依赖(如 :feature:foryou:impl -.-> :core:data)。完整的依赖图(mermaid 原文)如下:

---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :feature
    direction TB
    subgraph :feature:settings
      direction TB
      :feature:settings:impl[impl]:::android-library
    end
    subgraph :feature:foryou
      direction TB
      :feature:foryou:api[api]:::android-library
      :feature:foryou:impl[impl]:::android-library
    end
    subgraph :feature:bookmarks
      direction TB
      :feature:bookmarks:api[api]:::android-library
      :feature:bookmarks:impl[impl]:::android-library
    end
    subgraph :feature:search
      direction TB
      :feature:search:api[api]:::android-library
      :feature:search:impl[impl]:::android-library
    end
    subgraph :feature:interests
      direction TB
      :feature:interests:api[api]:::android-library
      :feature:interests:impl[impl]:::android-library
    end
    subgraph :feature:topic
      direction TB
      :feature:topic:api[api]:::android-library
      :feature:topic:impl[impl]:::android-library
    end
  end
  subgraph :sync
    direction TB
    :sync:work[work]:::android-library
  end
  subgraph :core
    direction TB
    :core:analytics[analytics]:::android-library
    :core:common[common]:::jvm-library
    :core:data[data]:::android-library
    :core:database[database]:::android-library
    :core:datastore[datastore]:::android-library
    :core:datastore-proto[datastore-proto]:::jvm-library
    :core:designsystem[designsystem]:::android-library
    :core:domain[domain]:::android-library
    :core:model[model]:::jvm-library
    :core:navigation[navigation]:::android-library
    :core:network[network]:::android-library
    :core:notifications[notifications]:::android-library
    :core:ui[ui]:::android-library
  end
  :benchmarks[benchmarks]:::android-test
  :app[app]:::android-application

从图中可见:app 聚合了全部 feature(foryou/bookmarks/interests/search/topic/settings)与 core 能力模块;各 feature 的 api 分层依赖 :core:navigationimpl 分层依赖 :core:data:core:designsystem:core:ui 及各自的 api。这种 api/impl 双模块 + 显式依赖边约束,保证 Profile 生成与基准测试的对象(app)与线上发布产物保持一致。

构建配置:从 build.gradle.kts 看测试基础设施

:benchmarks/build.gradle.kts 的配置决定了测试的形态,几个关键点值得展开:

android {
    namespace = "com.google.samples.apps.nowinandroid.benchmarks"
    defaultConfig {
        minSdk = 28
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        buildConfigField("String", "APP_BUILD_TYPE_SUFFIX", "\"\"")
    }
    buildFeatures { buildConfig = true }
    // 与 app 保持一致的 flavor 维度,用于区分 demo/prod
    configureFlavors(this) { flavor ->
        buildConfigField("String", "APP_FLAVOR_SUFFIX",
            "\"${flavor.applicationIdSuffix ?: ""}\"")
    }
    testOptions.managedDevices.localDevices {
        create("pixel6Api33") {
            device = "Pixel 6"; apiLevel = 33; systemImageSource = "aosp"
        }
    }
    targetProjectPath = ":app"
    experimentalProperties["android.experimental.self-instrumenting"] = true
}

baselineProfile {
    managedDevices.clear()
    managedDevices += "pixel6Api33"
    useConnectedDevices = false
}
  • minSdk = 28:Macrobenchmark 要求被测设备 API 28 以上(冷启动与 Profile 采集的硬性前提)。
  • self-instrumenting:启用后,测试模块与被测应用可处于同一进程运行,无需为 app 注入独立测试包。
  • managed device(GMD):固定使用 pixel6Api33(Pixel 6 / API 33 / aosp 镜像),并以 useConnectedDevices = false 强制走云端托管设备而非连接真机——注释明确说明这是为了"本地与 CI 构建的一致性"。
  • flavor 同步:benchmarksapp 共享同一套 flavor 维度,使得 Profile 既能在 prod 上生成(贴近线上真实数据),又能在 demo 上运行基准(使用稳定假数据,结果可复现)。

依赖声明集中在 gradle/libs.versions.tomlbenchmarks/build.gradle.ktsandroidx.benchmark.macro(Macrobenchmark API)、androidx.test.uiautomator(UI 自动化交互)、androidx.test.ext/rules/runner/espresso-core 等,插件侧使用 androidx.baselineprofile(版本引用 androidxMacroBenchmark,见 libs.versions.toml)。

应用侧集成:Profile 如何进入发布包

:app/build.gradle.kts 中的两处配置让 Profile 真正生效于发布构建:

release {
    // 发布构建时强制重新生成 Baseline Profile,保证 Profile 与代码同步
    baselineProfile.automaticGenerationDuringBuild = true
}
...
baselineProfile(projects.benchmarks)   // 依赖 :benchmarks 作为生成源

baselineProfile {
    // 常规 assemble 时不重复构建 Profile,仅 release 变体触发
    automaticGenerationDuringBuild = false
    // 通过 Startup Profile 启用 Dex 布局优化
    dexLayoutOptimization = true
}

配合 implementation(libs.androidx.profileinstaller)(见 app/build.gradle.kts),Profile 会在应用安装后被 profileinstaller 读取并应用。dexLayoutOptimization = true 意味着生成 Profile 时会把高频启动类/方法的信息写入 Startup Profile,实现 Dex 布局优化——这正是 StartupBaselineProfile.ktincludeInStartupProfile = true 参数所对应的机制。

启动性能基准:四种编译模式与自定义指标

:StartupBenchmark.kt 是模块中最具代表性的基准:它以冷启动(StartupMode.COLD)为场景,用同一套用户旅程对比四种 CompilationMode,从而量化 Baseline Profile 的价值:

测试方法 CompilationMode 含义
startupWithoutPreCompilation CompilationMode.None() 完全不预编译,纯 JIT,作为最差基线
startupWithPartialCompilationAndDisabledBaselineProfile Partial(baselineProfileMode = Disable, warmupIterations = 1) 部分预编译但禁用 Profile,用于隔离 Profile 本身的收益
startupPrecompiledWithBaselineProfile Partial(baselineProfileMode = Require) 应用现有 Baseline Profile(若缺失则失败),生产环境的真实形态
startupFullyPrecompiled CompilationMode.Full() 全量 AOT 预编译,作为理论上限参照

measureRepeated 的关键参数:iterations = 20(注释说明"更多迭代带来更高统计显著性")、startupMode = COLDsetupBlock 中先 pressHome() 再授权通知,测量块内通过 startActivityAndAllowNotifications() 拉起应用并用 forYouWaitForContent() 等待首帧内容渲染完成,从而捕获 Time To Full Display(完全显示耗时)。

指标方面,BaselineProfileMetrics.kt 自定义了三个:

val jitCompilationMetric = TraceSectionMetric("JIT Compiling %", label = "JIT compilation")
val classInitMetric = TraceSectionMetric("L%/%;", label = "ClassInit")
val allMetrics = listOf(StartupTimingMetric(), jitCompilationMetric, classInitMetric)
  • StartupTimingMetric():标准启动耗时(冷启动/首帧等);
  • JIT compilation:跟踪 JIT 编译耗时占比——"Profile 正确应用时该数值应下降";
  • ClassInit:跟踪类初始化耗时——"Profile 正确应用时该数值应下降"。

两个 TraceSectionMetric 配合使用,可精确定位 Profile 未生效时的时间损耗发生在 JIT 编译还是类加载初始化阶段,为性能优化提供归因证据。

滚动帧率与重组基准:FrameTimingMetric 的应用

滚动性能是 Lazy 列表应用的典型瓶颈,模块用 FrameTimingMetric 覆盖三条用户旅程:

  • For You 信息流ScrollForYouFeedBenchmark.ktWARM 启动下,先 forYouWaitForContent() 等待数据加载、forYouSelectTopics() 选中话题填充信息流,再 forYouScrollFeedDownUp() 完成一次下上往返滚动,iterations = 10。三个测试方法分别用 CompilationMode.None/Partial/Full 对照。
  • Interests 话题列表ScrollTopicListBenchmark.kt 导航到 Interests 页后,repeat(3) { interestsScrollTopicsDownUp() } 连续三次滚动。
  • 重组开销TopicsScreenRecompositionBenchmark.kt 在 Interests 页 repeat(3) { interestsToggleBookmarked() } 反复切换话题书签状态,通过 FrameTimingMetric 度量状态变更引发的重组是否带来掉帧——这正是 Compose 下"不必要的重组"问题的最直接量化手段。

功耗基准:PowerMetric 与明暗主题对比

:ScrollTopicListPowerMetricsBenchmark.kt 展示了功耗测量的完整写法,且标注 @RequiresApi(VERSION_CODES.Q)(功耗测量需 Android 10+):

private val categories = PowerCategory.entries
    .associateWith { PowerCategoryDisplayLevel.TOTAL }

metrics = listOf(FrameTimingMetric(), PowerMetric(PowerMetric.Energy(categories)))
iterations = 2

它同时注册 FrameTimingMetricPowerMetric(Energy(...)),对所有 PowerCategoryTOTAL 显示级别采集能耗。两个测试分别以浅色(setAppTheme(false))与深色(setAppTheme(true))主题运行同一滚动旅程,用于对比不同主题下帧率与能耗的差异。setAppTheme 的实现位于 ForYouActions.kt:进入设置页点击 "Dark/Light" 并确认 "OK",再等待 niaTopAppBar 出现。

Baseline Profile 生成:四个脚本化用户旅程

benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/ 下四个测试类分别覆盖应用四个核心场景,均通过 BaselineProfileRule().collect(PACKAGE_NAME) { ... } 录制执行路径生成 Profile:

Profile 类 覆盖旅程 源码
StartupBaselineProfile 冷启动(includeInStartupProfile = true,启用 Dex 布局优化) StartupBaselineProfile.kt
ForYouBaselineProfile For You 页:等待加载 → 选话题 → 滚动信息流 ForYouBaselineProfile.kt
BookmarksBaselineProfile Bookmarks(Saved)页导航 BookmarksBaselineProfile.kt
InterestsBaselineProfile Interests 页导航 + 话题列表滚动 InterestsBaselineProfile.kt

ForYouBaselineProfile 为例,其 Profile 块复用了与滚动基准完全相同的动作序列(forYouWaitForContent → forYouSelectTopics(true) → forYouScrollFeedDownUp),体现了"基准测试与 Profile 生成共享同一套 CUJ 定义"的良好实践——Profile 覆盖的就是你最关心的性能路径。生成动作结束后,app 的 release 构建会把结果合并进 APK(baselineProfile.automaticGenerationDuringBuild = true),后续发布无需手工维护 Profile 文件。

UI 交互封装:可复用的动作库与辅助函数

模块将 UiAutomator 交互抽象为按功能域组织的扩展函数,供基准与 Profile 共用:

  • 通用层GeneralActions.kt):startActivityAndWait() + 通知授权包装为 startActivityAndAllowNotifications();Android 13+(SDK_INT >= TIRAMISU)下通过 device.executeShellCommand("pm grant $packageName android.permission.POST_NOTIFICATIONS") 直接授权,避免点击系统弹窗的不确定性;getTopAppBar() 按资源 id niaTopAppBar 等待并返回应用顶栏。
  • For You 域ForYouActions.kt):forYouWaitForContent() 先等待 loadingWheel 消失,再等待 forYou:topicSelection 出现,并对其调用自定义条件 untilHasChildren() 最长 60 秒确保数据真正加载完成;forYouSelectTopics() 遍历话题列表,通过 By.checkable(true) 判定选中态,对未选中项点击、对已选中项在 recheckTopicsIfChecked 时连点两次以保持基线一致。
  • 滚动动作Utils.kt):flingElementDownUp(element)setGestureMargin(displayWidth / 5) 预留侧边距后执行 fling(Direction.DOWN)fling(Direction.UP)——注释指出这是"防止触发系统手势导航"。
  • 自定义等待条件UiAutomatorHelpers.kt):untilHasChildren(count, op) 支持 AT_LEAST / EXACTLY / AT_MOST 三种子节点数量断言,弥补 UiAutomator 内置条件的不足;文件特意放在 androidx.test.uiautomator 包内以访问包级私有方法。
  • 包名处理Utils.kt):PACKAGE_NAMEcom.google.samples.apps.nowinandroid + BuildConfig.APP_FLAVOR_SUFFIX 拼接,确保 demo/prod flavor 下都命中正确的被测应用。

此外 BookmarksActions.ktgoToBookmarksScreen()InterestsActions.ktgoToInterestsScreen()/interestsScrollTopicsDownUp()/interestsToggleBookmarked() 共同构成模块的 UI 操作矩阵。

运行方式与结果查看

基于构建配置(benchmarks/build.gradle.kts),可以推断出标准运行入口:

  • 生成 Baseline Profile:执行 ./gradlew :app:generateBaselineProfile(或对 :benchmarks 的对应 Profile 任务),Gradle 会在 pixel6Api33 托管设备上依次运行四个 BaselineProfileRule 测试并产出 Profile 资源;
  • 运行全部基准测试:执行 ./gradlew :benchmarks:pixel6Api33BenchmarkAndroidTest(task 命名遵循"托管设备名 + BenchmarkAndroidTest"的 Gradle 规则),即可在托管设备上批量执行上述 Startup / Scroll / Recomposition / Power 基准;
  • 结果查看:如 StartupBenchmark.kt 的类注释所述,从 Android Studio 直接运行单个基准方法,即可查看启动耗时测量结果,并借助自动采集的系统 trace 分析冷启动阶段的性能瓶颈。

小结

:benchmarks 模块为 Now in Android 提供了"测量 — 归因 — 优化 — 固化"的完整性能工作流:通过四种 CompilationMode 的横向对比量化 Baseline Profile 收益,通过 StartupTimingMetric / FrameTimingMetric / PowerMetric / TraceSectionMetric 分别覆盖启动、滚动帧率、功耗与 JIT/类初始化归因,再以四个 BaselineProfileRule 用户旅程自动生成并内嵌 Profile 到发布包,最终配合 profileinstaller 与 Dex 布局优化提升真实用户体验。对任何多模块 Compose 应用而言,这套"基准与 Profile 共享 CUJ、GMD 保证结果一致、release 自动再生成"的工程模式都具备直接的迁移价值。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
34
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.21 K
2.81 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
945
1.86 K
docsdocs
暂无描述
Markdown
906
5.84 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
537
607
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
864
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
4.28 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.39 K
1.48 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
550
401
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.19 K
347