1.先从官方下载demohttps://github.com/android/codelab-android-performance/archive/refs/heads/main.zip
2.先用Android studio打开里面的baseline-profiles项目
3.运行一遍app,这里建议用模拟器,(Pixel 6 API 34)设备运行,因为基准配置 需要root权限,如果手机没有root,就用模拟器运行。
4.运行时会报This version (1.4.5) of the Compose Compiler requires Kotlin version 1.8.20 but you appear to be using Kotlin version 1.9.22 which is not known to be compatible. Please consult the Compose-Kotlin compatibility map located at https://developer.android.com/jetpack/androidx/releases/compose-kotlin to choose a compatible version pair (or `suppressKotlinVersionCompatibilityCheck` but don't say I didn't warn you!).
5.修改libs.versions.toml里的 kotlin = "1.9.22" 改为 kotlin = "1.8.20"
6.再运行一下,private fun NavBackStackEntry.lifecycleIsResumed() = this.getLifecycle().currentState == Lifecycle.State.RESUMED 这里的getLifecycle()会报错,改成lifecycle
7.再运行一次,这一次应该可以启动成功了,图片不显示可以忽悠不管,因为这个图片是从谷歌那边加载过来的,需要飞机,我们重点是基准配置,如果前面步骤碰见其他问题,基本与该项目无关,可以自行百度和AI解决。
8.点击File->new module 选择baseline profile generator ,如图,如果你是java党,可以右边改改配置,因人而异,我这里就选择默认的kotlin,直接点finish
9.如果你碰见 配置文件一片红,就删除,重新创建,放app下面,就不会爆红了,如果你没有遇见以下问题,可以跳过此步
如果删不掉,是因为app在引用,把这段去掉,点击sync now,再重新删一下
10.在baselineprofile的build.gradle.kts文件中修改,添加代码如下,代码解释:useConnectedDevices = false意思是否在真机上运行,由于真机没有root,只能在模拟器上运行,所以选择false就行,然后配置模拟器 ,记得导入
import com.android.build.api.dsl.ManagedVirtualDevice
Kotlin
import com.android.build.api.dsl.ManagedVirtualDevice
android {
.......................
.......................
targetProjectPath = ":app"
testOptions.managedDevices.devices {
create<ManagedVirtualDevice>("pixel6Api31") {
device = "Pixel 6"
apiLevel = 31
systemImageSource = "aosp"
}
}
}
// This is the configuration block for the Baseline Profile plugin.
// You can specify to run the generators on a managed devices or connected devices.
baselineProfile {
managedDevices += "pixel6Api31"
useConnectedDevices = false
}
如图下
11.在baselineprofile找到BaselineProfileGenerator类,自己项目可以根据自己情况更改,但是由于我们是demo,就演示一下,代码如下
Kotlin
@Test
fun generate() {
// This example works only with the variant with application id `com.example.baselineprofiles_codelab`."
rule.collect(
packageName = "com.example.baselineprofiles_codelab",
// See: https://d.android.com/topic/performance/baselineprofiles/dex-layout-optimizations
includeInStartupProfile = true
) {
// This block defines the app's critical user journey. Here we are interested in
// optimizing for app startup. But you can also navigate and scroll through your most important UI.
// Start default activity for your app
pressHome()
startActivityAndWait()
// TODO Write more interactions to optimize advanced journeys of your app.
// For example:
// 1. Wait until the content is asynchronously loaded
// 2. Scroll the feed content
// 3. Navigate to detail screen
// 1. Wait until the content is asynchronously loaded.
waitForAsyncContent()
// 2. Scroll the feed content.
scrollSnackListJourney()
// 3. Navigate to detail screen.
goToSnackDetailJourney()
// Check UiAutomator documentation for more information how to interact with the app.
// https://d.android.com/training/testing/other-components/ui-automator
}
}
fun MacrobenchmarkScope.waitForAsyncContent() {
device.wait(Until.hasObject(By.res("snack_list")), 5_000)
val contentList = device.findObject(By.res("snack_list"))
// Wait until a snack collection item within the list is rendered.
contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}
fun MacrobenchmarkScope.scrollSnackListJourney() {
val snackList = device.findObject(By.res("snack_list"))
// Set gesture margin to avoid triggering gesture navigation.
snackList.setGestureMargin(device.displayWidth / 5)
snackList.fling(Direction.DOWN)
device.waitForIdle()
}
fun MacrobenchmarkScope.goToSnackDetailJourney() {
val snackList = device.findObject(By.res("snack_list"))
val snacks = snackList.findObjects(By.res("snack_item"))
// Select snack from the list based on running iteration.
val index = (iteration ?: 0) % snacks.size
snacks[index].click()
// Wait until the screen is gone = the detail is shown.
device.wait(Until.gone(By.res("snack_list")), 5_000)
}
12.点击run按钮旁边三颗点,选择Edit Configurations...,然后点击左上角+,添加Gradle,在RUN下面一行添加:app:generateBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile,点击OK就行,然后运行,如图:
如果出现> 'compileNonMinifiedReleaseJavaWithJavac' task (current target is 1.8) and 'compileNonMinifiedReleaseKotlin' task (current target is 17) jvm target compatibility should be set to the same Java version.
Consider using JVM toolchain: https://kotl.in/gradle/jvm/toolchain
把compileOptions里的兼容版本改成对应的版本就行了,由于我是用的java 17,就改成17就行了,然后重新运行
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
如果没有出现上面问题,可以忽悠不管
13.运行大概需要等5-6分钟,如果太久了,建议重新运行一下,因设备而异,运行完成的话,在app项目的src->release->generated->baselineProfiles文件下,生成2个txt文件,一个是1.8W行-2.5W行的baseline-prof.txt文件和startup-prof.txt文件,因项目而异,如果基准配置更多,生成的可能更多,由于我们只生成了,异步加载,点击,滚动,差不多2W行
如图:
- 使用模拟器测试速度,在 baselineprofile 的 build.gradle.kts下的defaultConfig 里添加 testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR",代码解释,这段是用来印制模拟器的错误
defaultConfig {
minSdk = 28
targetSdk = 34
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR"
}
15.在baselineprofile 项目的StartupBenchmarks类里添加和前面基准配置的 代码一样,模拟和基准操作
代码如下:
Kotlin
private fun benchmark(compilationMode: CompilationMode) {
// This example works only with the variant with application id `com.example.baselineprofiles_codelab`."
rule.measureRepeated(
packageName = "com.example.baselineprofiles_codelab",
metrics = listOf(StartupTimingMetric()),
compilationMode = compilationMode,
startupMode = StartupMode.COLD,
iterations = 10,
setupBlock = {
pressHome()
},
measureBlock = {
startActivityAndWait()
waitForAsyncContent()
// 2. Scroll the feed content.
scrollSnackListJourney()
// 3. Navigate to detail screen.
goToSnackDetailJourney()
}
)
}
fun MacrobenchmarkScope.waitForAsyncContent() {
device.wait(Until.hasObject(By.res("snack_list")), 5_000)
val contentList = device.findObject(By.res("snack_list"))
// Wait until a snack collection item within the list is rendered.
contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}
fun MacrobenchmarkScope.scrollSnackListJourney() {
val snackList = device.findObject(By.res("snack_list"))
// Set gesture margin to avoid triggering gesture navigation.
snackList.setGestureMargin(device.displayWidth / 5)
snackList.fling(Direction.DOWN)
device.waitForIdle()
}
fun MacrobenchmarkScope.goToSnackDetailJourney() {
val snackList = device.findObject(By.res("snack_list"))
val snacks = snackList.findObjects(By.res("snack_item"))
// Select snack from the list based on running iteration.
val index = (iteration ?: 0) % snacks.size
snacks[index].click()
// Wait until the screen is gone = the detail is shown.
device.wait(Until.gone(By.res("snack_list")), 5_000)
}
16.然后,直接右键运行,这个测试类,在模拟器(Pixel 6 API 31) 以上运行,我建议在模拟器(Pixel 6 API 34)运行,因为API31,可能会报下面错误,如果出现了,就切到API34
如果出现了java.lang.IllegalStateException: Error: did not detect tracing on after 5000 ms ,我建议切换到模拟器(Pixel 6 API34),运行就不有问题17.运行效果如下
StartupBenchmarks_startupCompilationBaselineProfiles
timeToFullDisplayMs min 821.2, median 908.4, max 1,114.1
timeToInitialDisplayMs min 438.9, median 514.8, max 678.4
Traces: Iteration 0 1 2 3 4 5 6 7 8 9
Timed out waiting for process (com.example.baselineprofiles_codelab) to appear on Pixel_6_API_34 [emulator-5556].
WARNING: Running on Emulator
Benchmark is running on an emulator, which is not representative of
real user devices. Use a physical device to benchmark. Emulator
benchmark improvements might not carry over to a real user's
experience (or even regress real device performance).
StartupBenchmarks_startupCompilationNone
timeToFullDisplayMs min 984.5, median 1,157.2, max 1,257.2
timeToInitialDisplayMs min 498.9, median 606.3, max 668.4
Traces: Iteration 0 1 2 3 4 5 6 7 8 9
StartupBenchmarks_startupCompilationNone 表示没有基准上运行
StartupBenchmarks_startupCompilationBaselineProfiles表示在有基准上运行
1,157.2 ->908.4,提升大概200毫秒,差不多百分之20多,到这里教程就结束了,谢谢大家