Compare commits

...

7 Commits

13 changed files with 7663 additions and 25165 deletions

View File

@ -53,7 +53,10 @@ android {
initWith(buildTypes.getByName("release")) initWith(buildTypes.getByName("release"))
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
matchingFallbacks += listOf("release") matchingFallbacks += listOf("release")
isDebuggable = true // isDebuggable=false 使 macrobenchmark 在模拟器上稳定运行,且 Partial 编译模式可用。
// 代价是生成的 baseline-prof.txt 会包含 R8 混淆后的类名;功能上仍然有效。
// 若需要可人工维护的可读 profile请在真机上使用 debuggable build 或引入 Baseline Profile Gradle plugin。
isDebuggable = false
} }
} }
@ -91,6 +94,7 @@ android {
dependencies { dependencies {
implementation(project(":core")) implementation(project(":core"))
implementation(libs.androidx.core.splashscreen)
implementation(platform(libs.compose.bom)) implementation(platform(libs.compose.bom))
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.profileinstaller) implementation(libs.androidx.profileinstaller)

View File

@ -11,7 +11,8 @@
android:theme="@style/Theme.YaYa"> android:theme="@style/Theme.YaYa">
<activity <activity
android:exported="true" android:exported="true"
android:name=".MainActivity"> android:name=".MainActivity"
android:theme="@style/Theme.YaYa.Starting">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@ -2,16 +2,21 @@ package plus.rua.project
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.compose.ReportDrawn
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import plus.rua.project.ui.CalendarMonthView import plus.rua.project.ui.CalendarMonthView
import plus.rua.project.ui.theme.YaYaTheme import plus.rua.project.ui.theme.YaYaTheme
class MainActivity : BaseActivity() { class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
YaYaTheme { YaYaTheme {
// 在主题内的首次 composition 绘制完成后报告 fully drawn用于准确的启动时间度量。
ReportDrawn()
CalendarMonthView( CalendarMonthView(
onNavigateToAbout = { onNavigateToAbout = {
startActivityWithSlide(Intent(this, AboutActivity::class.java)) startActivityWithSlide(Intent(this, AboutActivity::class.java))

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
启动窗口背景使用的 Material 3 静态基准色Dark
Android 12+ 上 Compose 使用动态颜色,该值作为 fallback
在动态颜色获取前减少启动窗口与首帧的色差闪烁。
-->
<color name="splash_background">#FF1C1B1F</color>
</resources>

View File

@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.YaYa" parent="@android:style/Theme.Material.NoActionBar" /> <style name="Theme.YaYa" parent="@android:style/Theme.Material.NoActionBar" />
<style name="Theme.YaYa.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/splash_background</item>
<item name="postSplashScreenTheme">@style/Theme.YaYa</item>
</style>
</resources> </resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
启动窗口背景使用的 Material 3 静态基准色Light
Android 12+ 上 Compose 使用动态颜色,该值作为 fallback
在动态颜色获取前减少启动窗口与首帧的色差闪烁。
-->
<color name="splash_background">#FFFBFE</color>
</resources>

View File

@ -1,4 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.YaYa" parent="@android:style/Theme.Material.Light.NoActionBar" /> <style name="Theme.YaYa" parent="@android:style/Theme.Material.Light.NoActionBar" />
<!--
启动主题:用于 MAIN/LAUNCHER Activity。
继承 Theme.SplashScreen保证 Android 12 以下也能获得一致的启动窗口行为。
windowSplashScreenBackground 使用 Material 3 静态基准色,作为动态颜色不可用时
的 fallback以减少启动窗口与 Compose 首帧之间的色差闪烁。
-->
<style name="Theme.YaYa.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/splash_background</item>
<item name="postSplashScreenTheme">@style/Theme.YaYa</item>
</style>
</resources> </resources>

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ android-compileSdk = "37"
android-minSdk = "24" android-minSdk = "24"
android-targetSdk = "37" android-targetSdk = "37"
androidx-activity = "1.13.0" androidx-activity = "1.13.0"
androidx-core-splashscreen = "1.0.1"
androidx-espresso = "3.7.0" androidx-espresso = "3.7.0"
androidx-lifecycle = "2.10.0" androidx-lifecycle = "2.10.0"
androidx-testExt = "1.3.0" androidx-testExt = "1.3.0"
@ -22,6 +23,7 @@ versions = "0.54.0"
[libraries] [libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
androidx-benchmark-macro = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "benchmarkMacro" } androidx-benchmark-macro = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "benchmarkMacro" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splashscreen" }
androidx-lifecycle-runtimeCompose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtimeCompose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
androidx-lifecycle-viewmodelCompose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } androidx-lifecycle-viewmodelCompose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "profileinstaller" } androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "profileinstaller" }

View File

@ -11,6 +11,10 @@ android {
targetSdk = libs.versions.android.targetSdk.get().toInt() targetSdk = libs.versions.android.targetSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// 允许在模拟器 / debuggable target 上运行 macrobenchmark仅用于开发验证
// 正式发布 benchmark 请在物理设备、release 目标应用上执行。
testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR,DEBUGGABLE"
} }
compileOptions { compileOptions {

View File

@ -1,16 +1,19 @@
<!-- Parent: ../../../../../../../AGENTS.md --> <!-- Parent: ../../../../../../../AGENTS.md -->
<!-- Generated: 2026-05-22 | Updated: 2026-05-22 --> <!-- Generated: 2026-05-22 | Updated: 2026-06-15 -->
# baseline # baseline
## Purpose ## Purpose
Baseline Profile 自动生成器。通过 UI Automator 模拟完整用户交互路径冷启动、FAB 展开、年/月视图切换、日期选择、折叠/展开、关于页、开源许可页),生成 AOT 编译优化所需的 startup profile。 Baseline Profile 自动生成器与启动性能基准测试。包含:
- `BaselineProfileGenerator.kt`:通过 UI Automator 模拟核心用户交互路径冷启动、FAB 展开、显示调休切换、CalendarPager 翻页、日期选择、BottomCard 折叠/展开),生成 AOT 编译优化所需的 startup profile。
- `StartupBenchmark.kt`:冷启动性能基准测试,测量 `timeToInitialDisplay``timeToFullDisplay`
## Key Files ## Key Files
| File | Description | | File | Description |
|------|-------------| |------|-------------|
| `BaselineProfileGenerator.kt` | Profile 生成测试类,覆盖全部用户交互路径 | | `BaselineProfileGenerator.kt` | Profile 生成测试类,覆盖启动与核心交互路径 |
| `StartupBenchmark.kt` | 冷启动基准测试类,覆盖 Full/Partial/None 三种编译模式 |
## Subdirectories ## Subdirectories
@ -23,13 +26,15 @@ Baseline Profile 自动生成器。通过 UI Automator 模拟完整用户交互
- 每个操作后调用 `device.waitForIdle()` 等待动画完成 - 每个操作后调用 `device.waitForIdle()` 等待动画完成
### Testing Requirements ### Testing Requirements
- 运行:`./gradlew :macrobenchmark:updateBaselineProfile` - 生成 Profile`./gradlew :macrobenchmark:updateBaselineProfile`
- 运行冷启动基准(仅该类):`./gradlew :macrobenchmark:connectedBenchmarkAndroidTest --tests "plus.rua.project.baseline.StartupBenchmark"`
- 需要设备/模拟器连接,应用已安装在 benchmark 构建类型下 - 需要设备/模拟器连接,应用已安装在 benchmark 构建类型下
## Dependencies ## Dependencies
### External ### External
- `androidx.benchmark.macro.junit4.BaselineProfileRule` - `androidx.benchmark.macro.junit4.BaselineProfileRule`
- `androidx.benchmark.macro.junit4.MacrobenchmarkRule`
- `androidx.test.uiautomator` - `androidx.test.uiautomator`
<!-- MANUAL: --> <!-- MANUAL: -->

View File

@ -29,24 +29,19 @@ import org.junit.runner.RunWith
* 手动复制路径 * 手动复制路径
* `macrobenchmark/build/outputs/connected_android_test_additional_output/` * `macrobenchmark/build/outputs/connected_android_test_additional_output/`
* *
* 测试覆盖全部用户交互路径实现全量 AOT * 测试覆盖启动与核心用户交互路径实现关键路径 AOT
* 1. 冷启动 首帧渲染 * 1. 冷启动 首帧渲染
* 2. 显示调休切换 ON/OFFDayCell 大规模重组 + staggered 动画 * 2. 显示调休切换 ON/OFFDayCell 大规模重组 + staggered 动画
* 3. CalendarPager 翻页 "今天"按钮跳回 * 3. CalendarPager 翻页 "今天"按钮跳回
* 4. 跨月日期点击 自动跳转 * 4. 跨月日期点击 自动跳转
* 5. 月视图 年视图切换 * 5. DayCell 点击
* 6. 年视图翻年 "今年"按钮跳回 * 6. BottomCard 拖拽折叠到周视图
* 7. 年视图点击 MiniMonth 返回月视图 * 7. 周视图左右翻页
* 8. DayCell 点击 * 8. BottomCard 拖拽展开回月视图
* 9. BottomCard 拖拽折叠到周视图 * 9. CalendarPager 左右翻页
* 10. 周视图左右翻页 *
* 11. BottomCard 拖拽展开回月视图 * 年视图关于/开源许可工具/日期检查器等路径在部分模拟器上不稳定
* 12. 工具页面 日期检查器 * 暂时从生成流程中移除以保证 Baseline Profile 可稳定生成后续可在真机上扩展覆盖
* 13. DatePickerDialog 打开/确认
* 14. 日期检查器添加行 + 输入天数
* 15. 日期检查器滑动删除行
* 16. 关于页面 开源许可页面
* 17. 返回主界面 CalendarPager 翻页
*/ */
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator { class BaselineProfileGenerator {
@ -55,21 +50,11 @@ class BaselineProfileGenerator {
val baselineProfileRule = BaselineProfileRule() val baselineProfileRule = BaselineProfileRule()
private fun MacrobenchmarkScope.safeFindFab(): UiObject2? = private fun MacrobenchmarkScope.safeFindFab(): UiObject2? =
device.wait(Until.findObject(By.res("fab_menu")), 3000) device.wait(Until.findObject(By.res("fab_menu")), 5000)
private fun MacrobenchmarkScope.safeWaitCalendarPager(timeout: Long = 5000): UiObject2? = private fun MacrobenchmarkScope.safeWaitCalendarPager(timeout: Long = 5000): UiObject2? =
device.wait(Until.findObject(By.res("calendar_pager")), timeout) device.wait(Until.findObject(By.res("calendar_pager")), timeout)
private fun MacrobenchmarkScope.waitForMainActivity() {
for (i in 1..5) {
val pager = device.wait(Until.findObject(By.res("calendar_pager")), 2000)
if (pager != null) return
val fab = device.wait(Until.findObject(By.res("fab_menu")), 1000)
if (fab != null) return
Thread.sleep(500)
}
}
@Test @Test
fun generateAppStartupProfile() { fun generateAppStartupProfile() {
baselineProfileRule.collect( baselineProfileRule.collect(
@ -104,7 +89,8 @@ class BaselineProfileGenerator {
device.waitForIdle() device.waitForIdle()
// ── 4. 点击"今天"按钮跳回当月(覆盖 MonthHeader 今天按钮) ── // ── 4. 点击"今天"按钮跳回当月(覆盖 MonthHeader 今天按钮) ──
val todayBtn = device.wait(Until.findObject(By.text("今天")), 3000) // 模拟器上 UI 渲染/动画较慢,使用较长超时等待按钮渲染
val todayBtn = device.wait(Until.findObject(By.text("今天")), 10000)
assertNotNull("今天按钮必须出现", todayBtn) assertNotNull("今天按钮必须出现", todayBtn)
todayBtn!!.click() todayBtn!!.click()
device.waitForIdle() device.waitForIdle()
@ -125,64 +111,7 @@ class BaselineProfileGenerator {
device.waitForIdle() device.waitForIdle()
} }
// ── 7. 切换到年视图 ────────────────────────────────────── // ── 7. 点击 DayCell ────────────────────────────────────
val fab2 = safeFindFab()
assertNotNull("FAB 必须存在(年视图)", fab2)
fab2!!.click()
device.waitForIdle()
val yearViewItem = device.wait(
Until.findObject(By.text("年视图")), 3000
)
assertNotNull("年视图必须出现", yearViewItem)
yearViewItem!!.click()
val yearGrid = device.wait(
Until.findObject(By.res("year_grid")), 3000
)
assertNotNull("YearGridView 必须加载", yearGrid)
device.waitForIdle()
// ── 8. 左滑年视图 → 下一年HorizontalPager ─────────────
yearGrid!!.swipe(Direction.LEFT, 0.8f)
device.waitForIdle()
Thread.sleep(500)
// ── 9. 尝试点击"今年"按钮跳回当前年 ──────────────────
val thisYearBtn = device.wait(
Until.findObject(By.text("今年")), 2000
)
if (thisYearBtn != null && thisYearBtn.visibleBounds.height() > 0) {
thisYearBtn.click()
device.waitForIdle()
Thread.sleep(500)
}
// ── 10. 点击当前月份 MiniMonth 返回月视图 ───────────────
val now = java.time.LocalDate.now()
val currentMonthDesc = "${now.year}${now.monthValue}"
val miniMonth = device.wait(
Until.findObject(By.desc(currentMonthDesc)), 3000
)
if (miniMonth != null) {
miniMonth.click()
} else {
val yearGridRef = device.wait(Until.findObject(By.res("year_grid")), 3000)
if (yearGridRef != null) {
val ygBounds = yearGridRef.visibleBounds
val monthW = ygBounds.width() / 3
val monthH = ygBounds.height() / 4
val mIdx = now.monthValue - 1
device.click(
ygBounds.left + (mIdx % 3) * monthW + monthW / 2,
ygBounds.top + (mIdx / 3) * monthH + monthH / 2
)
}
}
Thread.sleep(1500)
device.waitForIdle()
safeWaitCalendarPager(5000)
device.waitForIdle()
// ── 11. 点击 DayCell ────────────────────────────────────
val todayCell = device.wait( val todayCell = device.wait(
Until.findObject(By.descContains("今天")), 3000 Until.findObject(By.descContains("今天")), 3000
) )
@ -197,7 +126,7 @@ class BaselineProfileGenerator {
} }
device.waitForIdle() device.waitForIdle()
// ── 12. 拖拽 BottomCard 折叠到周视图 ───────────────────── // ── 8. 拖拽 BottomCard 折叠到周视图 ─────────────────────
val bottomCard = device.wait(Until.findObject(By.res("bottom_card")), 5000) val bottomCard = device.wait(Until.findObject(By.res("bottom_card")), 5000)
assertNotNull("BottomCard 必须存在", bottomCard) assertNotNull("BottomCard 必须存在", bottomCard)
val bcBounds = bottomCard!!.visibleBounds val bcBounds = bottomCard!!.visibleBounds
@ -207,7 +136,7 @@ class BaselineProfileGenerator {
device.drag(cx, cy, cx, cy - dragDist, 20) device.drag(cx, cy, cx, cy - dragDist, 20)
device.waitForIdle() device.waitForIdle()
// ── 13. 周视图左右翻页 ────────────────────────────────── // ── 9. 周视图左右翻页 ──────────────────────────────────
val weekPager = safeWaitCalendarPager(3000) val weekPager = safeWaitCalendarPager(3000)
assertNotNull("周视图 CalendarPager 必须存在", weekPager) assertNotNull("周视图 CalendarPager 必须存在", weekPager)
weekPager!!.swipe(Direction.LEFT, 0.5f) weekPager!!.swipe(Direction.LEFT, 0.5f)
@ -215,11 +144,11 @@ class BaselineProfileGenerator {
weekPager.swipe(Direction.RIGHT, 0.5f) weekPager.swipe(Direction.RIGHT, 0.5f)
device.waitForIdle() device.waitForIdle()
// ── 14. 拖拽 BottomCard 展开回月视图 ───────────────────── // ── 10. 拖拽 BottomCard 展开回月视图 ─────────────────────
device.drag(cx, cy - dragDist, cx, cy, 20) device.drag(cx, cy - dragDist, cx, cy, 20)
device.waitForIdle() device.waitForIdle()
// ── 15. 切换"显示调休"OFF ──────────────────────────────── // ── 11. 切换"显示调休"OFF ────────────────────────────────
val fab3 = safeFindFab() val fab3 = safeFindFab()
assertNotNull("FAB 必须存在(关闭调休)", fab3) assertNotNull("FAB 必须存在(关闭调休)", fab3)
fab3!!.click() fab3!!.click()
@ -231,7 +160,7 @@ class BaselineProfileGenerator {
legalHolidayOff!!.click() legalHolidayOff!!.click()
device.waitForIdle() device.waitForIdle()
// ── 16. CalendarPager 左右翻页 ────────────────────────── // ── 12. CalendarPager 左右翻页 ──────────────────────────
val mainPager = safeWaitCalendarPager(5000) val mainPager = safeWaitCalendarPager(5000)
if (mainPager != null) { if (mainPager != null) {
mainPager.swipe(Direction.LEFT, 0.5f) mainPager.swipe(Direction.LEFT, 0.5f)
@ -240,101 +169,7 @@ class BaselineProfileGenerator {
device.waitForIdle() device.waitForIdle()
} }
// ── 17. 进入关于页面 ──────────────────────────────────── Log.d(TAG, "Baseline profile 生成完成,核心路径已覆盖")
val fab5 = safeFindFab()
assertNotNull("FAB 必须存在(关于)", fab5)
fab5!!.click()
device.waitForIdle()
val aboutButton = device.wait(
Until.findObject(By.text("关于")), 3000
)
assertNotNull("关于必须出现", aboutButton)
aboutButton!!.click()
device.waitForIdle()
// ── 18. 进入开源许可页面 ────────────────────────────────
val licensesButton = device.wait(
Until.findObject(By.text("开放源代码许可")), 3000
)
assertNotNull("开放源代码许可按钮必须存在", licensesButton)
licensesButton!!.click()
device.waitForIdle()
// ── 19. 等待许可列表加载 ────────────────────────────────
device.wait(Until.findObject(By.textContains("Apache")), 2000)
// ── 20. 返回关于页 ──────────────────────────────────────
device.pressBack()
device.waitForIdle()
// ── 21. 返回主界面 ──────────────────────────────────────
device.pressBack()
device.waitForIdle()
// ── 22. 进入工具页面 ────────────────────────────────────
val fab4 = safeFindFab()
assertNotNull("FAB 必须存在(工具)", fab4)
fab4!!.click()
device.waitForIdle()
val toolsButton = device.wait(
Until.findObject(By.text("工具")), 3000
)
assertNotNull("工具必须出现", toolsButton)
toolsButton!!.click()
device.waitForIdle()
// ── 23. 进入日期检查器 ──────────────────────────────────
val dateCheckerEntry = device.wait(
Until.findObject(By.res("tool_date_checker")), 3000
)
assertNotNull("日期检查器入口必须存在", dateCheckerEntry)
dateCheckerEntry!!.click()
device.waitForIdle()
// ── 24. 打开生产日期 DatePicker → 确定 ───────────────────
val datePickerBtn = device.wait(
Until.findObject(By.res("date_picker_button")), 3000
)
if (datePickerBtn != null) {
datePickerBtn.click()
device.waitForIdle()
val confirmBtn = device.wait(
Until.findObject(By.text("确定")), 2000
)
if (confirmBtn != null) {
confirmBtn.click()
device.waitForIdle()
}
}
// ── 25. FAB 添加新行 ────────────────────────────────────
val dateCheckerFab = device.wait(
Until.findObject(By.res("date_checker_fab")), 3000
)
assertNotNull("DateChecker FAB 必须存在", dateCheckerFab)
dateCheckerFab!!.click()
device.waitForIdle()
// ── 26. 在新行输入天数 ──────────────────────────────────
val screenW = device.displayWidth
val screenH = device.displayHeight
device.click((screenW * 0.35f).toInt(), (screenH * 0.80f).toInt())
Thread.sleep(500)
device.executeShellCommand("input text '90'")
device.waitForIdle()
device.click(screenW / 2, (screenH * 0.15f).toInt())
device.waitForIdle()
// ── 27. 滑动删除新行SwipeToDismiss ───────────────────
device.swipe(
(screenW * 0.85f).toInt(), (screenH * 0.75f).toInt(),
(screenW * 0.15f).toInt(), (screenH * 0.75f).toInt(),
30
)
Thread.sleep(800)
device.waitForIdle()
Log.d(TAG, "Baseline profile 生成完成,所有路径已覆盖")
} }
) )
} }

View File

@ -0,0 +1,54 @@
package plus.rua.project.baseline
import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.StartupTimingMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/**
* 冷启动性能基准测试
*
* 测量指标
* - timeToInitialDisplay intent 到首帧显示
* - timeToFullDisplay intent reportFullyDrawn()/ReportDrawn() 被触发
*
* 运行方式
* ```
* ./gradlew :macrobenchmark:connectedBenchmarkAndroidTest --tests "plus.rua.project.baseline.StartupBenchmark"
* ```
*/
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun startupColdFull() = startup(CompilationMode.Full())
@Test
fun startupColdPartial() = startup(CompilationMode.Partial())
@Test
fun startupColdNone() = startup(CompilationMode.None())
private fun startup(compilationMode: CompilationMode) {
benchmarkRule.measureRepeated(
packageName = "plus.rua.project",
metrics = listOf(StartupTimingMetric()),
compilationMode = compilationMode,
startupMode = StartupMode.COLD,
iterations = 5,
setupBlock = {
pressHome()
},
measureBlock = {
startActivityAndWait()
}
)
}
}