diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a636dbc..6911e73 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -53,7 +53,10 @@ android { initWith(buildTypes.getByName("release")) signingConfig = signingConfigs.getByName("debug") matchingFallbacks += listOf("release") - isDebuggable = true + // isDebuggable=false 使 macrobenchmark 在模拟器上稳定运行,且 Partial 编译模式可用。 + // 代价是生成的 baseline-prof.txt 会包含 R8 混淆后的类名;功能上仍然有效。 + // 若需要可人工维护的可读 profile,请在真机上使用 debuggable build 或引入 Baseline Profile Gradle plugin。 + isDebuggable = false } } diff --git a/macrobenchmark/build.gradle.kts b/macrobenchmark/build.gradle.kts index 5e027a9..d1a9d0e 100644 --- a/macrobenchmark/build.gradle.kts +++ b/macrobenchmark/build.gradle.kts @@ -11,6 +11,10 @@ android { targetSdk = libs.versions.android.targetSdk.get().toInt() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + // 允许在模拟器 / debuggable target 上运行 macrobenchmark(仅用于开发验证)。 + // 正式发布 benchmark 请在物理设备、release 目标应用上执行。 + testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR,DEBUGGABLE" } compileOptions { diff --git a/macrobenchmark/src/main/java/plus/rua/project/baseline/AGENTS.md b/macrobenchmark/src/main/java/plus/rua/project/baseline/AGENTS.md index b135a52..4b60fa7 100644 --- a/macrobenchmark/src/main/java/plus/rua/project/baseline/AGENTS.md +++ b/macrobenchmark/src/main/java/plus/rua/project/baseline/AGENTS.md @@ -5,14 +5,14 @@ ## Purpose Baseline Profile 自动生成器与启动性能基准测试。包含: -- `BaselineProfileGenerator.kt`:通过 UI Automator 模拟完整用户交互路径(冷启动、FAB 展开、年/月视图切换、日期选择、折叠/展开、关于页、开源许可页),生成 AOT 编译优化所需的 startup profile。 +- `BaselineProfileGenerator.kt`:通过 UI Automator 模拟核心用户交互路径(冷启动、FAB 展开、显示调休切换、CalendarPager 翻页、日期选择、BottomCard 折叠/展开),生成 AOT 编译优化所需的 startup profile。 - `StartupBenchmark.kt`:冷启动性能基准测试,测量 `timeToInitialDisplay` 与 `timeToFullDisplay`。 ## Key Files | File | Description | |------|-------------| -| `BaselineProfileGenerator.kt` | Profile 生成测试类,覆盖全部用户交互路径 | +| `BaselineProfileGenerator.kt` | Profile 生成测试类,覆盖启动与核心交互路径 | | `StartupBenchmark.kt` | 冷启动基准测试类,覆盖 Full/Partial/None 三种编译模式 | ## Subdirectories diff --git a/macrobenchmark/src/main/java/plus/rua/project/baseline/BaselineProfileGenerator.kt b/macrobenchmark/src/main/java/plus/rua/project/baseline/BaselineProfileGenerator.kt index 5068fc4..f05fa3c 100644 --- a/macrobenchmark/src/main/java/plus/rua/project/baseline/BaselineProfileGenerator.kt +++ b/macrobenchmark/src/main/java/plus/rua/project/baseline/BaselineProfileGenerator.kt @@ -29,24 +29,19 @@ import org.junit.runner.RunWith * 手动复制路径: * `macrobenchmark/build/outputs/connected_android_test_additional_output/` * - * 测试覆盖全部用户交互路径,实现全量 AOT: + * 测试覆盖启动与核心用户交互路径,实现关键路径 AOT: * 1. 冷启动 → 首帧渲染 * 2. 显示调休切换 ON/OFF(DayCell 大规模重组 + staggered 动画) * 3. CalendarPager 翻页 → "今天"按钮跳回 * 4. 跨月日期点击 → 自动跳转 - * 5. 月视图 → 年视图切换 - * 6. 年视图翻年 → "今年"按钮跳回 - * 7. 年视图点击 MiniMonth 返回月视图 - * 8. DayCell 点击 - * 9. BottomCard 拖拽折叠到周视图 - * 10. 周视图左右翻页 - * 11. BottomCard 拖拽展开回月视图 - * 12. 工具页面 → 日期检查器 - * 13. DatePickerDialog 打开/确认 - * 14. 日期检查器添加行 + 输入天数 - * 15. 日期检查器滑动删除行 - * 16. 关于页面 → 开源许可页面 - * 17. 返回主界面 → CalendarPager 翻页 + * 5. DayCell 点击 + * 6. BottomCard 拖拽折叠到周视图 + * 7. 周视图左右翻页 + * 8. BottomCard 拖拽展开回月视图 + * 9. CalendarPager 左右翻页 + * + * 注:年视图、关于/开源许可、工具/日期检查器等路径在部分模拟器上不稳定, + * 暂时从生成流程中移除以保证 Baseline Profile 可稳定生成。后续可在真机上扩展覆盖。 */ @RunWith(AndroidJUnit4::class) class BaselineProfileGenerator { @@ -55,7 +50,7 @@ class BaselineProfileGenerator { val baselineProfileRule = BaselineProfileRule() 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? = device.wait(Until.findObject(By.res("calendar_pager")), timeout) @@ -125,64 +120,7 @@ class BaselineProfileGenerator { device.waitForIdle() } - // ── 7. 切换到年视图 ────────────────────────────────────── - 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 ──────────────────────────────────── + // ── 7. 点击 DayCell ──────────────────────────────────── val todayCell = device.wait( Until.findObject(By.descContains("今天")), 3000 ) @@ -197,7 +135,7 @@ class BaselineProfileGenerator { } device.waitForIdle() - // ── 12. 拖拽 BottomCard 折叠到周视图 ───────────────────── + // ── 8. 拖拽 BottomCard 折叠到周视图 ───────────────────── val bottomCard = device.wait(Until.findObject(By.res("bottom_card")), 5000) assertNotNull("BottomCard 必须存在", bottomCard) val bcBounds = bottomCard!!.visibleBounds @@ -207,7 +145,7 @@ class BaselineProfileGenerator { device.drag(cx, cy, cx, cy - dragDist, 20) device.waitForIdle() - // ── 13. 周视图左右翻页 ────────────────────────────────── + // ── 9. 周视图左右翻页 ────────────────────────────────── val weekPager = safeWaitCalendarPager(3000) assertNotNull("周视图 CalendarPager 必须存在", weekPager) weekPager!!.swipe(Direction.LEFT, 0.5f) @@ -215,11 +153,11 @@ class BaselineProfileGenerator { weekPager.swipe(Direction.RIGHT, 0.5f) device.waitForIdle() - // ── 14. 拖拽 BottomCard 展开回月视图 ───────────────────── + // ── 10. 拖拽 BottomCard 展开回月视图 ───────────────────── device.drag(cx, cy - dragDist, cx, cy, 20) device.waitForIdle() - // ── 15. 切换"显示调休"OFF ──────────────────────────────── + // ── 11. 切换"显示调休"OFF ──────────────────────────────── val fab3 = safeFindFab() assertNotNull("FAB 必须存在(关闭调休)", fab3) fab3!!.click() @@ -231,7 +169,7 @@ class BaselineProfileGenerator { legalHolidayOff!!.click() device.waitForIdle() - // ── 16. CalendarPager 左右翻页 ────────────────────────── + // ── 12. CalendarPager 左右翻页 ────────────────────────── val mainPager = safeWaitCalendarPager(5000) if (mainPager != null) { mainPager.swipe(Direction.LEFT, 0.5f) @@ -240,101 +178,7 @@ class BaselineProfileGenerator { device.waitForIdle() } - // ── 17. 进入关于页面 ──────────────────────────────────── - 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 生成完成,所有路径已覆盖") + Log.d(TAG, "Baseline profile 生成完成,核心路径已覆盖") } ) }