Baseline Profile

新环境

  • 当前所使用的Android Studio的版本为Android Studio Panda 4 | 2025.3.4 Patch 1
  • Gradle的版本为8.11.1
  • AGP的版本为8.9.2

新建模块

在项目中创建一个Baseline Profile Generator模块,创建窗口中有多个参数,只说一下Target application,和Use Gradle Managed Device

其中,Target application是要生成的基准配置文件所对应的应用模块,Use Gradle Managed Device选中后,将模块设置为在自动管理的Android模拟器上运行基准配置文件生成器,使用真机的情况下,不需要勾选这个选项。完成后点击Finish

项目变化

完事之后,除了添加一个新模块之外,项目中还会发生如下的变化

  1. gradle目录下的libs.versions.toml配置文件
  2. 项目根目录下的build.gradle
  3. 项目根目录下的settings.gradle
  4. 项目根目录下的gradle.properties
  5. app模块的build.gradle

libs.versions.toml

会增加如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[versions]
uiautomator = "2.3.0"
benchmarkMacroJunit4 = "1.4.1"
baselineprofile = "1.4.1"
profileinstaller = "1.4.1"

[libraries]
androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
androidx-benchmark-macro-junit4 = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "benchmarkMacroJunit4" }
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" }

[plugins]
android-test = { id = "com.android.test", version.ref = "agp" }
baselineprofile = { id = "androidx.baselineprofile", version.ref = "baselineprofile" }

如果引入上面的配置,实际上相当于

1
2
3
4
5
6
7
8
9
10
// libraries
implementation 'androidx.test.uiautomator:uiautomator:2.3.0'
implementation 'androidx.benchmark:benchmark-macro-junit4:1.4.1'
implementation 'androidx.profileinstaller:profileinstaller:1.4.1'

// plugins
plugins {
id 'com.android.test' version '8.9.2' apply false
id 'androidx.baselineprofile' version '1.4.1' apply false
}

project:build.gradle

在项目根目录下的build.gradle文件中,会增加如下配置,通过apply false只声明插件版本而不实际应用它们,待需要时在app模块的build.gradle文件中再声明,后续声明时无需再注明版本号,在当前的gradleAGP版本中,通过alias声明插件版本,所以不明显

1
2
3
4
plugins {
alias(libs.plugins.android.test) apply false
alias(libs.plugins.baselineprofile) apply false
}

settings.gradle

没什么好说的,增加了一个模块的引用

1
include ':baselineprofile'

gradle.properties

gradle.properties文件中,会增加如下配置,启用Gradle的配置缓存功能,以加快构建速度

1
org.gradle.configuration-cache=true

app:build.gradle

app模块的build.gradle文件中会增加如下配置,其中:

  1. baselineProfile project(':baselineprofile')依赖项,让Gradle知道应该从哪个模块获取已生成的基准配置文件
  2. profileinstaller依赖项,应用首次启动时,它会读取并安装打包在APK中的基准配置文件,告诉Android Runtime(ART)哪些代码路径需要提前编译(AOT)
1
2
3
4
5
6
7
8
9
10
plugins {
// 新增
alias(libs.plugins.baselineprofile)
}

dependencies {
// 新增
implementation libs.androidx.profileinstaller
baselineProfile project(':baselineprofile')
}

Baseline Profile模块配置

Baseline Profile Generator模块的build.gradle文件中,简单分析一下如下配置

alias(libs.plugins.android.test)声明了这是一个测试模块

1
2
3
4
5
plugins {
alias(libs.plugins.android.test)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.baselineprofile)
}

targetProjectPath指定了要生成基准配置文件的目标应用模块,animationsDisabled则是自动关闭设备动画,保证测试结果稳定

1
2
3
4
5
6
7
8
android {
targetProjectPath = ":app"

testOptions {
// 自动关闭设备动画,保证测试结果稳定。
animationsDisabled = true
}
}

baselineProfile配置项中,启用useConnectedDevices选项,让生成器在连接的设备上运行测试,以收集基准配置文件数据

1
2
3
baselineProfile {
useConnectedDevices = true
}

如下依赖项,其中:

  1. benchmark-macro-junit4是基准测试框架
  2. espresso-coreuiautomator是用于编写用户界面测试的库
  3. junit是用于编写单元测试的库
1
2
3
4
5
6
dependencies {
implementation libs.androidx.benchmark.macro.junit4
implementation libs.androidx.espresso.core
implementation libs.androidx.junit
implementation libs.androidx.uiautomator
}

其他配置

实际上,在有了上面的配置之后,项目就已经具备了生成基准配置文件的能力了,但是在生成之前,我们还需要对baselineprofile模块进行一些额外的配置

app模块的build.gradle文件中,增加如下配置:

  • dexLayoutOptimization让启动热点代码尽可能位于DEX前部,减少启动时的随机I/O和页面加载时间,在较新的gradle版本中,这个配置项已经被默认启用。
  • automaticGenerationDuringBuild设置为false,避免在构建APK时自动采集Profile
1
2
3
4
5
6
baselineProfile {
// 让启动热点代码尽可能位于 DEX 前部
dexLayoutOptimization = true
// 构建 APK 时,不要自动采集 Profile
automaticGenerationDuringBuild = false
}

编写关键路径采集

BaselineProfileRule负责采集性能数据和Profile,并将其转换为基准配置文件格式,UIAutomator负责点击、滑动、长按、输入等用户界面交互操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@get:Rule
val rule = BaselineProfileRule()

@Test
fun generate() {
rule.collect(
// 采集关键路径的目标包名
packageName = TARGET_PACKAGE,
// 最多采集多少轮
maxIterations = 15,
// 连续多少轮结果一致,认为采集稳定,停止采集
stableIterations = 3,
// 生成文件名前缀 jqTools-baseline-prof.txt
outputFilePrefix = "jqTools",
// 是否生成 Startup Profile,即startup-prof.txt,告诉 ART 这些代码启动一定会执行,冷启动更快
includeInStartupProfile = true,
// Profile文件稳定性检查,为true时启动严格检查,要求baseline-prof文件完全一致
strictStability = false,
// 过滤导出的 Profile 条目,为 true 时保留,为 false 时忽略
filterPredicate = {
// 例如,忽略以下类
it !in listOf(
"android.app.ActivityThread\$AppBindData",
"android.app.ActivityThread\$ProviderKey",
)
}
) {
// 模拟按下 Home 键
pressHome()
// 启动 Activity
startActivityAndWait()

/*
* device 实际上是 UiDevice
* 等价于UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
*/

// 等待页面加载
device.waitForIdle()
}
}

查找text为 分类 的view并点击

1
2
3
device.findObject(
By.text("分类")
)?.click()

通过资源id匹配view并点击

1
2
3
4
5
6
7
device.findObject(
By.res(TARGET_PACKAGE, "btn_login")
)?.click()

device.findObject(
By.res("${TARGET_PACKAGE}:id/btn_login")
)?.click()

RecyclerView滚动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 通过资源 id 匹配 recyclerView
val list = device.findObject(
By.res(
TARGET_PACKAGE,
"rv_list"
)
)
// 获取 recyclerView 的可见区域
val rect = list.visibleBounds
// 滑动 recyclerView 3 次
device.swipe(
rect.centerX(),
rect.bottom - 100,
rect.centerX(),
rect.top + 100,
3
)

还有很多,用到的时候再查文档吧

生成基准配置文件

在旧的AGP版本中,执行如下命令(其中:baselineprofile是模块名),会在连接的设备上运行测试用例,收集性能数据,并生成基准配置文件

1
./gradlew :baselineprofile:generateBaselineProfile

但是AGP 8.2+以后,这个Taskapp模块上了,所以执行如下命令

1
./gradlew :app:generateBaselineProfile

也有可能Task名字变了,执行如下命令查看一下指定的模块中有没有相应的Task

1
./gradlew :baselineprofile:tasks --all

编写性能测试

在生成基准配置文件后,可以通过运行性能测试来验证其效果,在目标设备上自动运行app并监控其性能指标,如启动时间、内存使用情况

MacrobenchmarkRule负责控制测试流程、统计性能指标、处理测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@get:Rule
val rule = MacrobenchmarkRule()

/**
* 关闭所有编译优化,执行性能测试
*/
@Test
fun startupCompilationNone() =
benchmark(CompilationMode.None())

/**
* 强制使用 Baseline Profile
*/
@Test
fun startupCompilationBaselineProfiles() =
benchmark(CompilationMode.Partial(BaselineProfileMode.Require))

/**
* 启动性能测试
* @param compilationMode 优化方式
*/
private fun benchmark(compilationMode: CompilationMode) {
// 重复执行性能测试,收集数据计算平均值
rule.measureRepeated(
packageName = TARGET_PACKAGE, // 测试的应用包名
metrics = listOf(StartupTimingMetric()), // 统计启动时间
compilationMode = compilationMode, // 优化方式
startupMode = StartupMode.COLD, // 启动方式,冷启动、热启动等
iterations = 10, // 测试次数
setupBlock = { // 每次测试前执行
pressHome()
},
measureBlock = { // 自动化测试代码
startActivityAndWait()
}
)
}

执行性能测试时,注意将项目variants变体设置为benchmarkRelease,以确保测试的是优化后的版本

打包

生成基准配置文件之后,如果配置文件路径没在main目录下,需要将生成的基准配置文件复制到app模块的src/main/baseline-prof.txt路径下,之后可以通过如下命令将其打包到APK

1
./gradlew :app:assembleRelease

旧环境

  • 使用的Android Studio的版本为Android Studio Panda 4 | 2025.3.4 Patch 1
  • Gradle的版本为8.0
  • AGP的版本为8.0.2

新建模块

同当下新环境的模块创建

项目变化

完事之后,除了添加一个新模块之外,项目中还会发生如下的变化

  1. 项目根目录下的build.gradle
  2. 项目根目录下的settings.gradle
  3. 项目根目录下的gradle.properties
  4. app模块的build.gradle

project:build.gradle

在项目根目录下的build.gradle文件中,会增加如下配置,通过apply false只声明插件版本而不实际应用它们,待需要时在app模块的build.gradle文件中再声明,后续声明时无需再注明版本号,在当前的gradleAGP版本中

1
2
3
4
plugins {
id 'com.android.test' version '8.0.2' apply false
id 'androidx.baselineprofile' version '1.2.4' apply false
}

settings.gradle

同当下新环境的 settings.gradle

gradle.properties

同当下新环境的 gradle.properties

app:build.gradle

app模块的build.gradle文件中会增加如下配置,其中:

  1. baselineProfile project(':baselineprofile')依赖项,让Gradle知道应该从哪个模块获取已生成的基准配置文件
  2. profileinstaller依赖项,应用首次启动时,它会读取并安装打包在APK中的基准配置文件,告诉Android Runtime(ART)哪些代码路径需要提前编译(AOT)
1
2
3
4
5
6
7
8
9
10
plugins {
// 新增
id 'androidx.baselineprofile'
}

dependencies {
// 新增
implementation 'androidx.profileinstaller:profileinstaller:1.4.1'
baselineProfile project(':baselineprofile')
}

Baseline Profile模块配置

Baseline Profile Generator模块的build.gradle文件中,简单分析一下如下配置

id 'androidx.baselineprofile'声明了这是一个测试模块

1
2
3
4
plugins {
id 'com.android.test'
id 'androidx.baselineprofile'
}

targetProjectPath指定了要生成基准配置文件的目标应用模块,animationsDisabled则是自动关闭设备动画,保证测试结果稳定

1
2
3
4
5
6
7
8
android {
targetProjectPath = ":app"

testOptions {
// 自动关闭设备动画,保证测试结果稳定。
animationsDisabled = true
}
}

baselineProfile配置项中,启用useConnectedDevices选项,让生成器在连接的设备上运行测试,以收集基准配置文件数据

1
2
3
baselineProfile {
useConnectedDevices = true
}

如下依赖项,其中:

  1. benchmark-macro-junit4是基准测试框架
  2. espresso-coreuiautomator是用于编写用户界面测试的库
  3. junit是用于编写单元测试的库
1
2
3
4
5
6
dependencies {
implementation 'androidx.benchmark:benchmark-macro-junit4:1.4.1'
implementation 'androidx.test.espresso:espresso-core:3.7.0'
implementation 'androidx.test.ext:junit:1.3.0'
implementation 'androidx.test.uiautomator:uiautomator:2.3.0'
}

其他配置

同当下新环境的其他配置

编写关键路径采集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Rule
public BaselineProfileRule rule = new BaselineProfileRule();
@Test
public void generate() {
rule.collect(
// 采集关键路径的目标包名
TARGET_PACKAGE_NAME,
// 最多采集多少轮
15,
// 连续多少轮结果一致,认为采集稳定,停止采集
3,
// 生成文件名前缀 jqTools-baseline-prof.txt
"jqTools",
// 是否生成 Startup Profile,即startup-prof.txt,告诉 ART 这些代码启动一定会执行,冷启动更快
true,
// Profile文件稳定性检查,为true时启动严格检查,要求baseline-prof文件完全一致
false,
// 过滤导出的 Profile 条目,为 true 时保留,为 false 时忽略
s -> {
Set<String> knownClasses = Set.of(
"xxin.jqTools.activity.MainActivity$onCreate$1",
"xxin.jqTools.activity.FlashActivity$onCreate$1"
);
return !knownClasses.contains(s);
},
// 执行的操作
scope -> {
// 模拟按下 Home 键
scope.pressHome();
// 启动 Activity
scope.startActivityAndWait();
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// 等待页面加载
device.waitForIdle();

return Unit.INSTANCE;
});
}

生成基准配置文件

同当下新环境的生成基准配置文件

编写性能测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Rule
public MacrobenchmarkRule rule = new MacrobenchmarkRule();
/**
* 关闭所有编译优化,执行性能测试
*/
@Test
public void startupCompilationNone() {
benchmark(new CompilationMode.None());
}
/**
* 强制使用 Baseline Profile
*/
@Test
public void startupCompilationBaselineProfiles() {
benchmark(new CompilationMode.Partial(BaselineProfileMode.Require));
}
/**
* 启动性能测试
* @param compilationMode 优化方式
*/
private void benchmark(CompilationMode compilationMode) {
// 重复执行性能测试,收集数据计算平均值
rule.measureRepeated(
// 测试的应用包名
TARGET_PACKAGE_NAME,
// 统计启动时间
Collections.singletonList(new StartupTimingMetric()),
// 优化方式
compilationMode,
// 启动方式,冷启动、热启动等
StartupMode.COLD,
// 测试次数
10,
// 每次测试前执行
setupScope -> {
setupScope.pressHome();
return Unit.INSTANCE;
},
// 自动化测试代码
measureScope -> {
measureScope.startActivityAndWait();
return Unit.INSTANCE;
}
);
}

打包

同当下新环境的打包

参考:

  1. https://developer.android.com/topic/performance/baselineprofiles/overview
  2. https://codelabs.developers.google.com/android-baseline-profiles-improve