Android NDK开发详解之调试和性能分析的手动创建和衡量基准配置文件

  • 手动定义配置文件规则
  • 规则语法
  • 基准配置文件规则支持的类型
  • 手动衡量应用改进
  • 基准配置文件和 Profgen
  • 使用 Profgen-cli 构建紧凑二进制配置文件
  • 配置文件格式和平台版本
  • 以传统方式安装基准配置文件
  • 结合使用 install-multiple 与 DexMetadata
  • Jetpack ProfileInstaller
  • 旁加载基准配置文件
  • 转储二进制配置文件的内容


我们强烈建议利用 Jetpack Macrobenchmark 库来实现配置文件规则的自动生成,以减少手动工作并提高一般可伸缩性。不过,您可以在您的应用中手动创建和衡量配置文件规则。

手动定义配置文件规则

您可以在应用中或在库模块中手动定义配置文件规则,方法是在 src/main 目录中创建一个名为 baseline-prof.txt 的文件。这是包含 AndroidManifest.xml 文件的同一个文件夹。

该文件中的每行指定了一条规则。每条规则均表示一种模式,用于匹配应用或库中需要优化的方法或类。

这些规则的语法是使用 adb shell profman --dump-classes-and-methods 时所用的人类可读懂的 ART 配置文件格式 (HRF) 的超集。该语法与描述符和签名的语法相似,但允许使用通配符以简化规则编写过程。

以下示例展示了 Jetpack Compose 库包含的一些基准配置文件规则:

HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V
HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
HLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V
PLandroidx/compose/runtime/CompositionImpl;->applyChanges()V
HLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I
Landroidx/compose/runtime/ComposerImpl;

规则语法

这些规则采用两种形式,一种用于方法,一种用于类:

[FLAGS][CLASS_DESCRIPTOR]->[METHOD_SIGNATURE]

类规则使用以下格式:

[CLASS_DESCRIPTOR]

有关详细说明,请参阅下表:

Android工程中配置emdebfile_android

这些格式可以包含通配符,以便让单个规则能够涵盖多个方法或类。如需获取在 Android Studio 中使用规则语法编写代码方面的指导帮助,请参阅 Android 基准配置文件插件。

下面显示了一个通配符规则示例:

HSPLandroidx/compose/ui/layout/**->**(**)**

基准配置文件规则支持的类型

基准配置文件规则支持以下类型。如需详细了解这些类型,请参阅 Dalvik 可执行文件 (DEX) 格式。

Android工程中配置emdebfile_数据结构_02

此外,库还可以定义打包到 AAR 工件中的规则。当您构建 APK 来包含这些工件时,相应规则会合并在一起(类似于清单的合并方式),并编译成 APK 专用的紧凑型二进制 ART 配置文件。

对于搭载 Android 9(API 级别 28)或 Android 7(API 级别 24)并且使用 ProfileInstaller 的设备,当在设备上使用 APK 时,ART 会利用此配置文件在应用安装时对应用的特定子集进行 AOT 编译。

手动衡量应用改进

注意:为了提高稳定性和准确性,我们建议使用 Macrobenchmark 来衡量性能影响,因为 Macrobenchmark 可以在一个循环中反复进行衡量、捕获性能调试轨迹,并提高可靠性(例如通过清除操作系统的磁盘缓存)。
我们强烈建议您通过基准测试来衡量应用改进。不过,如果您想手动衡量改进,可以先衡量一下未优化的应用启动,以便作为参考。

PACKAGE_NAME=
# Force Stop App
adb shell am force-stop $PACKAGE_NAME
# Reset compiled state
adb shell cmd package compile --reset $PACKAGE_NAME
# Measure App startup
# This corresponds to `Time to initial display` metric.
adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \
 | grep "TotalTime"

接下来,旁加载基准配置文件。

注意:此工作流仅适用于 Android 9 (API 28) 到 Android 11 (API 30)。如需了解详情,请参阅各种 Android 版本的编译行为。

# Unzip the Release APK first.
unzip release.apk
# Create a ZIP archive.
# The name should match the name of the APK.
# Copy `baseline.prof{m}` and rename it `primary.prof{m}`.
cp assets/dexopt/baseline.prof primary.prof
cp assets/dexopt/baseline.profm primary.profm
# Create an archive.
zip -r release.dm primary.prof primary.profm
# Confirm that release.dm only contains the two profile files:
unzip -l release.dm
# Archive:  release.dm
#   Length      Date    Time    Name
# ---------  ---------- -----   ----
#      3885  1980-12-31 17:01   primary.prof
#      1024  1980-12-31 17:01   primary.profm
# ---------                     -------
#                               2 files
# Install APK + Profile together.
adb install-multiple release.apk release.dm

如需验证软件包在安装时是否已优化,请运行以下命令:

# Check dexopt state.
adb shell dumpsys package dexopt | grep -A 1 $PACKAGE_NAME

输出中必须声明软件包已经过编译:

[]
  path: /data/app/~~YvNxUxuP2e5xA6EGtM5i9A==/-zQ0tkJN8tDrEZXTlrDUSBg==/base.apk
  arm64: [status=speed-profile] [reason=install-dm]

现在,您可以像以前一样衡量应用启动性能,而无需重置编译状态。切勿重置软件包的编译状态。

# Force stop app
adb shell am force-stop $PACKAGE_NAME
# Measure app startup
adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \
 | grep "TotalTime"

注意:为了提高稳定性和准确性,建议使用 Macrobenchmark 来衡量性能影响,因为 Macrobenchmark 可以在一个循环中反复进行衡量、捕获性能调试的轨迹,并提高可靠性(例如,通过清除操作系统的磁盘缓存)。

基准配置文件和 Profgen

本部分介绍了如何使用 Profgen 工具构建基准配置文件的紧凑二进制版本。

Profgen-cli 有助于配置文件编译、自省和转译 ART 配置文件,无论目标 SDK 版本如何,都可以在 Android 设备上安装。

Profgen-cli 是一个 CLI,可将基准配置文件的 HRF 编译为其编译格式。该 CLI 还会作为 Android SDK 的一部分提供给 cmdline-tools 库。

studio-main 分支提供以下功能:

➜ ../cmdline-tools/latest/bin
apkanalyzer
avdmanager
lint
profgen
retrace
screenshot2
sdkmanager

使用 Profgen-cli 构建紧凑二进制配置文件

Profgen-cli 提供的命令是 bin、validate 和 dumpProfile。如需查看可用命令,请使用 profgen --help:

➜  profgen --help
Usage: profgen options_list
Subcommands:
    bin - Generate Binary Profile
    validate - Validate Profile
    dumpProfile - Dump a binary profile to a HRF

Options:
    --help, -h -> Usage info

使用 bin 命令生成紧凑二进制配置文件。以下是一个调用示例:

profgen bin ./baseline-prof.txt \
  --apk ./release.apk \
  --map ./obfuscation-map.txt \
  --profile-format v0_1_0_p \
  --output ./baseline.prof \

如需查看可用选项,请使用 profgen bin options_list:

Usage: profgen bin options_list
Arguments:
    profile -> File path to Human Readable profile { String }
Options:
    --apk, -a -> File path to apk (always required) { String }
    --output, -o -> File path to generated binary profile (always required)
    --map, -m -> File path to name obfuscation map { String }
    --output-meta, -om -> File path to generated metadata output { String }
    --profile-format, -pf [V0_1_0_P] -> The ART profile format version
      { Value should be one of [
         v0_1_5_s, v0_1_0_p, v0_0_9_omr1, v0_0_5_o, v0_0_1_n
        ]
      }
    --help, -h -> Usage info

第一个参数表示 baseline-prof.txt HRF 的路径。

Profgen-cli 还需要 APK 的发布 build 的路径,以及用于在使用 R8 或 Proguard 时对 APK 进行混淆处理的混淆映射。这样,profgen 便可以在构建已编译的配置文件时将 HRF 中的源符号转换为相应的经过混淆处理的名称。

由于 ART 配置文件格式不向前或向后兼容,因此请提供配置文件格式,以便 profgen 将配置文件元数据 (profm) 打包在一起,在需要时用于将一种 ART 配置文件格式转码为另一种格式。

配置文件格式和平台版本

注意:将配置文件捆绑到 assets 文件夹中时,应始终采用 v0_1_0_p 格式。
选择配置文件格式时,会出现以下选项:

Android工程中配置emdebfile_数据结构_03

将 baseline.prof 和 baseline.profm 输出文件复制到 APK 的 assets 或 dexopt 文件夹中。

混淆映射
仅当 HRF 使用源符号时,才需要提供混淆映射。如果 HRF 是从已经过混淆处理的发布 build 生成且不需要映射,则可以忽略此选项并将输出复制到 assets 或 dexopt 文件夹。

以传统方式安装基准配置文件

按照历来的做法,基准配置文件会通过两种方式提供给设备。

结合使用 install-multiple 与 DexMetadata

在搭载 API 28 及更高版本的设备上,Play 客户端为要安装的 APK 版本下载 APK 和 DexMetadata (DM) 载荷。DM 包含向设备上的软件包管理器传递的配置文件信息。

该 APK 和 DM 会在单次安装会话期间使用如下方式进行安装:

adb install-multiple base.apk 

注意:正确的配置文件 DM 载荷会根据发出 APK 下载请求的设备 SDK 版本来提供。Play 会将封装为 v0_1_0_p 的配置文件转码成用于提供正确版本的每个已知配置文件版本,从而生成一个元组。

Jetpack ProfileInstaller

在搭载 API 级别 29 及更高版本的设备上,Jetpack ProfileInstaller 库提供了一种替代机制,可在 APK 安装到设备上之后安装打包到 assets 或 dexopt 中的配置文件。ProfileInstaller 由 ProfileInstallReceiver 或应用直接调用。

ProfileInstaller 库会根据目标设备 SDK 版本对配置文件进行转码,并将配置文件复制到设备上的 cur 目录(设备上的 ART 配置文件的软件包专用暂存目录)中。

设备处于空闲状态后,设备上名为 bg-dexopt 的进程就会对配置文件进行编译。

注意:即使只有 Android P 设备及更高版本支持 Play 使用 install-multiple 分发基准配置文件,ProfileInstaller 也可以将 ART 配置文件向后移植到 Android N。因此,在使用基准配置文件时,请务必声明对 ProfileInstaller 库的依赖项。

旁加载基准配置文件

本部分介绍了如何在具有 APK 的情况下安装基准配置文件。

使用 androidx.profileinstaller 进行广播
在搭载 API 24 及更高版本的设备上,您可以广播一条命令来安装配置文件:

# Broadcast the install profile command - moves binary profile from assets
#     to a location where ART uses it for the next compile.
#     When successful, the following command prints "1":
adb shell am broadcast \
    -a androidx.profileinstaller.action.INSTALL_PROFILE \
    <pkg>/androidx.profileinstaller.ProfileInstallReceiver

# Kill the process
am force-stop <pkg>

# Compile the package based on profile
adb shell cmd package compile -f -m speed-profile <pkg>

大多数具有基准配置文件的 APK 中都没有 ProfileInstaller(Play 中约有 45 万个应用,其中 77,000 个应用有 ProfileInstaller),但它实际上存在于每个使用 Compose 的 APK 中。这是因为库可以提供配置文件,而无需声明 ProfileInstaller 的依赖项。从 Jetpack 开始,在每个具有配置文件的库中都会添加依赖项。

结合使用 install-multiple 与 Profgen 或 DexMetaData
在搭载 API 28 及更高版本的设备上,您可以旁加载基准配置文件,而无需在应用中使用 ProfileInstaller 库。

为此,请使用 Profgen-cli:

profgen extractProfile \
        --apk app-release.apk \
        --output-dex-metadata app-release.dm \
        --profile-format V0_1_5_S # Select based on device and the preceding table.

# Install APK and the profile together
adb install-multiple appname-release.apk appname-release.dm

如需支持 APK 拆分,请针对每个 APK 运行一次上述提取配置文件步骤。在安装时,请传递每个 APK 和关联的 .dm 文件,确保 APK 和 .dm 的名称一致:

adb install-multiple appname-base.apk appname- \
appname-split1.apk appname-split1.dm

验证
如需验证配置文件是否已正确安装,您可以按照手动衡量应用改进中的步骤进行操作。

转储二进制配置文件的内容

如需自省基准配置文件的紧凑二进制版本的内容,请使用 Profgen-cli dumpProfile 选项:

Usage: profgen dumpProfile options_list
Options:
    --profile, -p -> File path to the binary profile (always required)
    --apk, -a -> File path to apk (always required) { String }
    --map, -m -> File path to name obfuscation map { String }
    --strict, -s [true] -> Strict mode
    --output, -o -> File path for the HRF (always required) { String }
    --help, -h -> Usage info

dumpProfile 需要 APK,因为紧凑二进制表示法仅存储 DEX 偏移,因此需要它们来重构类和方法名称。

严格模式默认处于启用状态,此操作会对配置文件与 APK 中的 DEX 文件进行兼容性检查。如果您尝试调试其他工具生成的配置文件,可能会遇到兼容性检查失败问题,导致您无法转储文件以进行调查。如果遇到这种情况,您可以使用 --strict false 停用严格模式。不过,在大多数情况下,您应该启用严格模式。

混淆映射是可选的;它有助于将经过混淆处理的符号重新映射到人类可读的版本,以便于使用。