借助 Macrobenchmark,您可以直接针对在搭载 Android M (API 23) 或更高版本系统的设备上运行的应用编写启动和运行时性能测试。
建议您将 Macrobenchmark 与最新版本的 Android Studio(2021.1.1 或更高版本)搭配使用,因为该版本的 IDE 中提供了可与 Macrobenchmark 集成的新功能。使用早期版本 Android Studio 的用户可以根据本主题后面部分的额外说明来处理跟踪文件。
基准测试通过 Macrobenchmark 库中的 MacrobenchmarkRule
JUnit4 规则 API 提供:
Kotlin
@get:Rule val benchmarkRule = MacrobenchmarkRule() @Test fun startup() = benchmarkRule.measureRepeated( packageName = "mypackage.myapp", metrics = listOf(StartupTimingMetric()), iterations = 5, startupMode = StartupMode.COLD ) { // this = MacrobenchmarkScope pressHome() val intent = Intent() intent.setPackage("mypackage.myapp") intent.setAction("mypackage.myapp.myaction") startActivityAndWait(intent) }
Java
@Rule MacrobenchmarkRule benchmarkRule = MacrobenchmarkRule() @Test void startup() = benchmarkRule.measureRepeated( "mypackage.myapp", // packageName listOf(StartupTimingMetric()), // metrics 5, // iterations StartupMode.COLD // startupMode ) { scope -> scope.pressHome() Intent intent = Intent() intent.setPackage("mypackage.myapp") intent.setAction("mypackage.myapp.myaction") scope.startActivityAndWait(intent) }
指标会直接显示在 Android Studio 中,还会针对使用 CI 的情况以 JSON 文件形式输出。
模块设置
宏基准需要一个独立于应用代码的 com.android.test
模块来负责运行衡量应用的测试。
Bumblebee
在 Android Studio Bumblebee 中,我们提供了一个模板来简化 Macrobenchmark 模块设置。
添加新模块
基准化分析模块模板会自动在项目中创建一个模块(包含一个示例启动基准),用于衡量通过应用模块构建的应用。
如需使用模块模板创建新模块,请执行以下操作:
在 Android Studio 的 Project 面板中右键点击您的项目或模块,然后依次点击 New > Module。
选择 Benchmark Module。
您可以自定义目标应用(要进行基准测试的应用),以及新的 Macrobenchmark 模块的软件包和模块名称。
点击 Finish。
Arctic Fox
在 Arctic Fox 中,您要创建一个库模块,然后将其转换为测试模块。
添加新模块
向项目添加新模块。此模块将包含您的 Macrobenchmark 测试。
- 在 Android Studio 的 Project 面板中右键点击您的项目或模块,然后依次点击 New > Module。
- 在 Templates 窗格中选择 Android Library。
- 输入
macrobenchmark
作为模块名称。 - 将 Minimum SDK 设置为 API 23: Android M
- 点击 Finish。
修改 Gradle 文件
按如下所示自定义 Macrobenchmark 模块的 build.gradle
:
- 将插件从
com.android.library
更改为com.android.test
。 - 在
android {}
代码块中添加其他所需的测试模块属性: - 将所有名为
testImplementation
或androidTestImplementation
的依赖项更改为implementation
。 - 添加 Macrobenchmark 库的依赖项:
implementation 'androidx.benchmark:benchmark-macro-junit4:1.1.0-alpha13'
- 在
android {}
代码块之后、dependencies {}
代码块之前,添加以下代码:
targetProjectPath = ":app" // Note that your module name may be different // Enable the benchmark to run separately from the app process experimentalProperties["android.experimental.self-instrumenting"] = true buildTypes { // Declare a build type (release) to match the target app's build type release { debuggable = true } }
androidComponents { beforeVariants(selector().all()) { // Enable only the benchmark buildType, since we only want to measure // release-like build performance (should match app buildType) enabled = buildType == 'benchmark' } }
简化目录结构
在 com.android.test
模块中,对于所有测试,只有一个源目录。删除包括 src/test
和 src/androidTest
在内的其他源目录,因为系统不会使用这些目录。
有关参考信息,请参阅 Macrobenchmark 模块示例。
创建宏基准
在该模块中定义一个新的测试类,并填入应用的软件包名称:
@RunWith(AndroidJUnit4::class) class SampleStartupBenchmark { @get:Rule val benchmarkRule = MacrobenchmarkRule() @Test fun startup() = benchmarkRule.measureRepeated( packageName = "mypackage.myapp", metrics = listOf(StartupTimingMetric()), iterations = 5, startupMode = StartupMode.COLD ) { // this = MacrobenchmarkScope pressHome() val intent = Intent() intent.setPackage("mypackage.myapp") intent.setAction("mypackage.myapp.myaction") startActivityAndWait(intent) } }
设置应用
若要对应用(称为该宏基准测试的目标)进行基准测试,该应用必须处于可分析状态,这样系统才能读取详细的跟踪信息。您可以在应用的 AndroidManifest.xml
的 <application>
标记中启用此状态:
<application ... >
<!-- Profileable to enable Macrobenchmark profiling -->
<!-- Suppress AndroidElementNotAllowed -->
<profileable android:shell="true"/>
...
</application>
尽可能将进行基准测试的应用配置得接近用户体验。将其设置为不可调试,最好开启“缩减大小”功能,以提高性能。通常,您可以通过创建 release
变体的 benchmark
副本来实现此目的,该副本将执行同样的操作,但使用 debug
密钥在本地签名:
buildTypes {
benchmark {
// duplicate any release build type settings for measurement accuracy,
// such as "minifyEnabled" and "proguardFiles" in this block
debuggable false
signingConfig signingConfigs.debug
}
}
执行 Gradle 同步,打开左侧的 Build Variants 面板,然后选择应用和 Macrobenchmark 模块的 benchmark
变体。
这可确保运行基准测试的操作将会构建并测试应用的正确变体:
针对内部 activity 运行 Macrobenchmark 需要执行一个额外步骤。如需对 exported=false
内部 activity 进行基准测试,需要将 setupBlock
传递给 MacrobenchmarkRule.measureRepeated()
,以导航到要进行基准测试的代码,并使用 measureBlock
来调用要衡量的实际 activity 启动或滚动操作。
自定义宏基准
CompilationMode
宏基准可以指定 CompilationMode
,用于定义应该将应用的多大部分从 DEX 字节码(APK 中的字节码格式)预编译为机器代码(类似于预编译的 C++)。
默认情况下,系统会使用 CompilationMode.DEFAULT
运行宏基准,这会在 Android Nougat (API 24) 及更高版本上安装基准配置文件(如果有),并在 Android Marshmallow (API 23) 及更低版本上完全编译 APK(这是默认系统行为)。
如果目标应用同时包含基准配置文件和 ProfileInstaller 库,您可以安装基准配置文件。
在 Android Nougat (API 24) 及更高版本中,您可以自定义 CompilationMode
以影响设备上的预编译量,从而模拟不同级别的预先 (AOT) 编译或 JIT 缓存。请参阅 CompilationMode.Full
、CompilationMode.Partial
和 CompilationMode.None
。
此功能基于 ART 编译命令构建而成。每次基准测试都会在开始之前清除性能分析数据,以确保基准测试之间互不干扰。
启动
如需执行 activity 启动,您可以向 measureRepeated()
函数传递一种预定义的启动模式(COLD
、WARM
或 HOT
中的一种)。此参数会更改 activity 的启动方式,以及测试开始时的进程状态。
如需详细了解启动类型,请参阅 Android Vitals 启动文档。
滚动和动画
与大多数 Android 界面测试不同,Macrobenchmark 测试在独立于应用本身的进程中运行。必须这样做才能实现终止应用进程和使用 shell 命令进行编译等目的。
您可以使用 UI Automator 库或其他可以通过测试进程控制目标应用的机制来驱动您的应用。Espresso 或 ActivityScenario
等方法不起作用,因为它们需要在与应用共享的进程中运行。
以下示例会使用资源 ID 查找 RecyclerView
,然后向下滚动几次:
@Test
fun measureScroll() {
benchmarkRule.measureRepeated(
packageName = "mypackage.myapp",
metrics = listOf(FrameTimingMetric()),
iterations = 5,
setupBlock = {
// before starting to measure, navigate to the UI to be measured
val intent = Intent()
intent.action = ACTION
startActivityAndWait(intent)
}
) {
val recycler = device.findObject(By.res("mypackage.myapp", "recycler_id"))
// Set gesture margin to avoid triggering gesture nav
// with input events from automation.
recycler.setGestureMargin(device.displayWidth / 5)
// Scroll down several times
for (i in 1..10) {
recycler.scroll(Direction.DOWN, 2f)
device.waitForIdle()
}
}
}
由于测试指定了 FrameTimingMetric
,因此系统会记录帧时间并以帧时间分布简略摘要的形式进行报告:50%、90%、95% 和 99%。
您的基准并不一定要滚动界面。例如,它也可以运行动画。此外,它也不需要特地使用 UI Automator;只要帧是由视图系统生成的,其中包括 Compose 生成的帧,系统就会收集性能指标。请注意,诸如 Espresso 之类的进程内机制不起作用,因为应用需要从测试应用进程驱动。
运行宏基准
在 Android Studio 中运行测试,以衡量应用在设备上的性能。请注意,必须在实体设备上运行测试,而不是在模拟器上,因为模拟器无法产生代表最终用户体验的性能数据。
如需了解如何在持续集成环境中运行和监控基准测试,请参阅在持续集成环境中运行基准测试部分。
您也可以执行 connectedCheck
命令,从命令行运行所有基准测试:
$ ./gradlew :macrobenchmark:connectedCheck
配置错误
如果应用配置有误(可调试或不可分析),Macrobenchmark 测试就会抛出错误,而不会报告不正确或不完整的衡量结果。
您可以使用 androidx.benchmark.suppressErrors
参数抑制这些错误。
如果尝试衡量模拟器,或在电量不足的设备上进行衡量,系统也会抛出错误,因为这可能会影响核心可用性和时钟速度。
检查跟踪文件
每次衡量的迭代均会捕获单独的系统跟踪文件。点击 Test Results 窗格中的其中一个链接,可以打开这些结果跟踪文件,如本主题 Jetpack Macrobenchmark 部分中的图片所示。跟踪文件加载完成后,Android Studio 会提示您选择要分析的进程。系统会预先填充目标应用进程:
跟踪文件加载完成后,Studio 将在 CPU 性能剖析器工具中显示结果:
手动访问跟踪文件
如果您使用的是旧版 Android Studio(2020.3.1 之前的版本),或者要使用 Perfetto 工具分析跟踪文件,则还需要执行几个额外的步骤。
首先,从设备中提取跟踪文件:
# The following command pulls all files ending in .perfetto-trace from the directory
# hierarchy starting at the root /storage/emulated/0/Android.
$ adb shell find /storage/emulated/0/Android/ -name "*.perfetto-trace" \
| tr -d '\r' | xargs -n1 adb pull
请注意,如果您使用 additionalTestOutputDir
参数自定义输出文件路径,则输出路径可能会有所不同。您可以在 logcat 中查找跟踪文件路径日志,了解它们的写入位置。例如:
I PerfettoCapture: Writing to /storage/emulated/0/Android/data/androidx.benchmark.integration.macrobenchmark.test/cache/TrivialStartupBenchmark_startup[mode=COLD]_iter002.perfetto-trace.
如果您改为使用 Gradle 命令行(例如 ./gradlew macrobenchmark:connectedCheck
)调用测试,则可以将测试结果文件复制到主机系统上的测试输出目录。为此,请将以下行添加到项目的 gradle.properties
文件中:
android.enableAdditionalTestOutput=true
测试运行的结果文件会显示在项目的 build 目录中,如下所示:
build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/<device-name>/TrivialStartupBenchmark_startup[mode=COLD]_iter002.perfetto-trace
将跟踪文件复制到主机系统后,您可以在 Android Studio 中通过依次点击 File > Open 菜单打开此跟踪文件。此时系统会显示性能剖析器工具视图,和上一部分中显示的一样。
此外,您也可以选择使用 Perfetto 工具。您可以使用 Perfetto 检查跟踪期间整个设备上发生的所有进程,而 Android Studio 的 CPU 性能剖析器只能检查单个进程。
利用自定义事件改进跟踪数据
利用自定义跟踪事件进行应用插桩非常有用,这些事件会显示在跟踪报告的其余部分,有助于找出应用特有的问题。如需详细了解如何创建自定义跟踪事件,请参阅定义自定义事件指南。
在持续集成环境中运行基准测试
不使用 Gradle 时,在持续集成环境中运行测试是很常见的,如果您使用的是其他构建系统,则还可能会在本地运行测试。本部分介绍了如何针对在运行期间使用持续集成的情况配置 Macrobenchmark。
结果文件:JSON 和跟踪文件
Macrobenchmark 会输出一个 JSON 文件和多个跟踪文件:每个 MacrobenchmarkRule.measureRepeated
循环的每次衡量迭代对应一个文件。
您可以通过在运行时传入以下插桩参数来定义这些文件的写入位置:
-e additionalTestOutputDir "device_path_you_can_write_to"
请注意,为简单起见,您可以指定一个 /sdcard/
上的路径,但必须在 Macrobenchmark 模块中将 requestLegacyExternalStorage
设置为 true
,以停用分区存储:
<manifest ... >
<application android:requestLegacyExternalStorage="true" ... >
...
</application>
</manifest>
或者,传递插桩参数以绕过分区存储进行测试:
-e no-isolated-storage 1
JSON 示例
下面显示了单次启动基准测试的 JSON 输出示例:
{
"context": {
"build": {
"device": "walleye",
"fingerprint": "google/walleye/walleye:10/QQ3A.200805.001/6578210:userdebug/dev-keys",
"model": "Pixel 2",
"version": {
"sdk": 29
}
},
"cpuCoreCount": 8,
"cpuLocked": false,
"cpuMaxFreqHz": 2457600000,
"memTotalBytes": 3834605568,
"sustainedPerformanceModeEnabled": false
},
"benchmarks": [
{
"name": "startup",
"params": {},
"className": "androidx.benchmark.integration.macrobenchmark.SampleStartupBenchmark",
"totalRunTimeNs": 77969052767,
"metrics": {
"startupMs": {
"minimum": 228,
"maximum": 283,
"median": 242,
"runs": [
238,
283,
256,
228,
242
]
}
},
"warmupIterations": 3,
"repeatIterations": 5,
"thermalThrottleSleepSeconds": 0
}
]
}
其他资源
GitHub 上提供了一个示例项目,该项目是 Android/performance-samples 代码库的一部分。
如需了解如何检测回归问题,请参阅在 CI 中使用基准来解决回归问题。
反馈
如需针对 Jetpack Macrobenchmark 报告问题或提交功能请求,请参阅公开问题跟踪器。