Compare commits
7 Commits
5bf8a85f7a
...
bcc59ca7a0
| Author | SHA1 | Date | |
|---|---|---|---|
| bcc59ca7a0 | |||
| 91068ded1c | |||
| 499a2eb0c7 | |||
| 66be0be3c4 | |||
| ccffcfb052 | |||
| 7ae0e80baf | |||
| e97c2f3015 |
@ -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)
|
||||||
|
|||||||
@ -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" />
|
||||||
|
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
9
app/src/main/res/values-night/colors.xml
Normal file
9
app/src/main/res/values-night/colors.xml
Normal 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>
|
||||||
@ -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>
|
||||||
|
|||||||
9
app/src/main/res/values/colors.xml
Normal file
9
app/src/main/res/values/colors.xml
Normal 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>
|
||||||
@ -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
@ -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" }
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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: -->
|
||||||
|
|||||||
@ -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/OFF(DayCell 大规模重组 + staggered 动画)
|
* 2. 显示调休切换 ON/OFF(DayCell 大规模重组 + 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 生成完成,所有路径已覆盖")
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user