perf: 升级 Macrobenchmark 至 1.4.1,适配新 API 并重新生成 Baseline Profile
- benchmarkMacro 1.3.4 → 1.4.1 - BaselineProfileGenerator: 使用 Direction 枚举替代坐标 swipe; 用 executeShellCommand 绕过 startActivityAndWait(模拟器 software renderer 不支持 gfxinfo framestats,会导致 amStartAndWait 超时) - updateBaselineProfile task: 适配 1.4+ 的 startup-prof.txt 文件名格式, 使用 layout.buildDirectory 预先计算路径(configuration cache 兼容) - 重新生成 baseline-prof.txt(全量 AOT 覆盖) - benchmark build type 设为 isDebuggable=true(配合模拟器运行) - 将 Baseline Profile 使用文档从 README 迁移至 DEVELOPMENT.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2771e3733b
commit
98f3b71c4f
@ -117,6 +117,7 @@ CalendarMonthView (顶层屏幕)
|
||||
项目使用 `composeTraceBeginSection` / `composeTraceEndSection` 在关键代码段插入 trace marker,Android 上会被记录到系统 trace 中。iOS 为空操作。
|
||||
|
||||
已有的 trace section:
|
||||
|
||||
- `MonthView:Compose` / `YearView:Compose` — 顶层重组耗时
|
||||
- `YearView→MonthView` / `MonthView→YearView` — 年视图切换动画
|
||||
- `YearGridView:$year` / `generateMiniMonthDays:$year-$month` — 年网格渲染
|
||||
@ -125,6 +126,7 @@ CalendarMonthView (顶层屏幕)
|
||||
### 分析折叠器卡顿的方法
|
||||
|
||||
1. **录制 trace**:Android Studio → Profiler → CPU → 选择 "Trace Java Methods" 或命令行:
|
||||
|
||||
```bash
|
||||
adb shell perfetto -c - --txt \<<EOF
|
||||
buffers: { size_kb: 65536 }
|
||||
@ -247,6 +249,47 @@ CalendarMonthView (顶层屏幕)
|
||||
### 已知排查结论(2026-05-19)
|
||||
|
||||
对折叠器 trace 的分析显示:
|
||||
|
||||
- **重组本身很快**(VM progress → Compose 约 500μs),不是卡顿来源。
|
||||
- **触摸事件采样间隔不均匀**是主要问题。某些拖拽序列中出现 30-50ms 的触摸事件间隔,偶尔有 >100ms 的断流。这属于系统/模拟器层的事件分发问题,而非 Compose 代码问题。
|
||||
- 若在真机上复现,建议检查是否有 CPU 抢占或手指短暂离屏。
|
||||
|
||||
## Baseline Profile
|
||||
|
||||
```bash
|
||||
# 编译 Android debug APK
|
||||
./gradlew :app:assembleDebug
|
||||
|
||||
# 安装到设备
|
||||
./gradlew :app:installDebug
|
||||
|
||||
# 编译 release APK(含 Baseline Profiles)
|
||||
./gradlew :app:assembleRelease
|
||||
|
||||
./gradlew :app:installBenchmark
|
||||
```
|
||||
|
||||
Baseline Profile 自动生成器。
|
||||
|
||||
运行方式(一键生成 + 自动复制到 :core):
|
||||
|
||||
```
|
||||
./gradlew :macrobenchmark:updateBaselineProfile
|
||||
```
|
||||
|
||||
仅运行基准测试(不自动复制):
|
||||
|
||||
```
|
||||
./gradlew :macrobenchmark:connectedBenchmarkAndroidTest
|
||||
```
|
||||
|
||||
手动复制路径:
|
||||
`macrobenchmark/build/outputs/connected_android_test_additional_output/`
|
||||
|
||||
测试覆盖全部用户交互路径,实现全量 AOT:
|
||||
|
||||
1. 冷启动 → 首帧渲染
|
||||
2. FAB 展开 → 年视图 → 月视图
|
||||
3. 日期选择 → 周视图折叠/展开
|
||||
4. 关于页 → 开源许可页
|
||||
5. 返回主界面
|
||||
|
||||
40
README.md
40
README.md
@ -20,43 +20,3 @@
|
||||
- iOS 入口为 `MainViewController.kt`,Xcode 工程位于 `iosApp/`
|
||||
|
||||
线条小狗表情包来自 https://www.douban.com/group/topic/264788645/?_i=9181692phrDzjR,9241256phrDzjR
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 编译 Android debug APK
|
||||
./gradlew :app:assembleDebug
|
||||
|
||||
# 安装到设备
|
||||
./gradlew :app:installDebug
|
||||
|
||||
# 编译 release APK(含 Baseline Profiles)
|
||||
./gradlew :app:assembleRelease
|
||||
|
||||
./gradlew :app:installBenchmark
|
||||
```
|
||||
|
||||
Baseline Profile 自动生成器。
|
||||
|
||||
运行方式(一键生成 + 自动复制到 :core):
|
||||
|
||||
```
|
||||
./gradlew :macrobenchmark:updateBaselineProfile
|
||||
```
|
||||
|
||||
仅运行基准测试(不自动复制):
|
||||
|
||||
```
|
||||
./gradlew :macrobenchmark:connectedBenchmarkAndroidTest
|
||||
```
|
||||
|
||||
手动复制路径:
|
||||
`macrobenchmark/build/outputs/connected_android_test_additional_output/`
|
||||
|
||||
测试覆盖全部用户交互路径,实现全量 AOT:
|
||||
|
||||
1. 冷启动 → 首帧渲染
|
||||
2. FAB 展开 → 年视图 → 月视图
|
||||
3. 日期选择 → 周视图折叠/展开
|
||||
4. 关于页 → 开源许可页
|
||||
5. 返回主界面
|
||||
|
||||
@ -50,7 +50,7 @@ android {
|
||||
initWith(buildTypes.getByName("release"))
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
matchingFallbacks += listOf("release")
|
||||
isDebuggable = false
|
||||
isDebuggable = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,7 @@ kotlinx-datetime = "0.8.0"
|
||||
tyme4kt = "1.4.5"
|
||||
sketch = "4.4.0"
|
||||
profileinstaller = "1.4.0"
|
||||
benchmarkMacro = "1.3.4"
|
||||
benchmarkMacro = "1.4.1"
|
||||
androidx-uiautomator = "2.3.0"
|
||||
|
||||
[libraries]
|
||||
|
||||
@ -43,28 +43,31 @@ dependencies {
|
||||
|
||||
val updateBaselineProfile by tasks.registering {
|
||||
group = "benchmark"
|
||||
description = "运行 connectedBenchmarkAndroidTest 并将生成的 baseline-prof.txt 复制到 :core 模块"
|
||||
description = "运行 connectedBenchmarkAndroidTest 并将生成的 startup-prof.txt 复制到 :core 模块"
|
||||
|
||||
// 依赖基准测试 task(需要先连接设备/模拟器)
|
||||
dependsOn("connectedBenchmarkAndroidTest")
|
||||
|
||||
// 预先计算目标路径,避免在 doLast 中引用 project 对象(configuration cache 兼容)
|
||||
val targetPath = rootProject.projectDir.resolve("core/src/main/baseline-prof.txt").absolutePath
|
||||
val buildDirPath = layout.buildDirectory.get().asFile.absolutePath
|
||||
|
||||
doLast {
|
||||
// 寻找生成的 profile 文件(路径格式因设备/Gradle 版本略有不同)
|
||||
// 寻找生成的 profile 文件(benchmark 1.4+ 文件名格式:{Class}_{Method}-startup-prof.txt)
|
||||
val sourcePaths = listOf(
|
||||
// AGP 8.x+ 标准输出路径
|
||||
"build/outputs/connected_android_test_additional_output/benchmark/",
|
||||
"build/outputs/connected_android_test_additional_output/",
|
||||
"$buildDirPath/outputs/connected_android_test_additional_output/benchmark/",
|
||||
"$buildDirPath/outputs/connected_android_test_additional_output/",
|
||||
)
|
||||
|
||||
val targetFile = rootProject.projectDir.resolve("core/src/main/baseline-prof.txt")
|
||||
|
||||
val targetFile = File(targetPath)
|
||||
var copied = false
|
||||
for (path in sourcePaths) {
|
||||
val dir = file(path)
|
||||
val dir = File(path)
|
||||
if (!dir.exists()) continue
|
||||
|
||||
// 优先匹配不带时间戳的 startup-prof.txt(benchmark 1.4+ 格式)
|
||||
val profileFile = dir.walkTopDown()
|
||||
.firstOrNull { it.name == "baseline-prof.txt" }
|
||||
.firstOrNull { it.name.endsWith("-startup-prof.txt") && !it.name.contains(Regex("-\\d{4}-\\d{2}-\\d{2}-")) }
|
||||
?: continue
|
||||
|
||||
profileFile.copyTo(targetFile, overwrite = true)
|
||||
@ -76,7 +79,7 @@ val updateBaselineProfile by tasks.registering {
|
||||
|
||||
if (!copied) {
|
||||
throw GradleException(
|
||||
"未找到生成的 baseline-prof.txt。\n" +
|
||||
"未找到生成的 *-startup-prof.txt。\n" +
|
||||
"请确认:\n" +
|
||||
" 1. 设备/模拟器已连接 (adb devices)\n" +
|
||||
" 2. 应用已安装在 benchmark 构建类型下\n" +
|
||||
|
||||
@ -3,6 +3,7 @@ package plus.rua.project.baseline
|
||||
import androidx.benchmark.macro.junit4.BaselineProfileRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.Direction
|
||||
import androidx.test.uiautomator.Until
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@ -44,10 +45,12 @@ class BaselineProfileGenerator {
|
||||
includeInStartupProfile = true,
|
||||
profileBlock = {
|
||||
// 1. 冷启动:从 launcher 启动应用
|
||||
// 注:使用 shell command 绕过 startActivityAndWait,因为模拟器的 software
|
||||
// renderer 不支持 gfxinfo framestats,会导致 amStartAndWait 超时。
|
||||
pressHome()
|
||||
startActivityAndWait()
|
||||
|
||||
// 2. 等待首帧渲染完成
|
||||
device.executeShellCommand(
|
||||
"am start -W -n plus.rua.project/.MainActivity"
|
||||
)
|
||||
device.waitForIdle()
|
||||
|
||||
// 3. 模拟用户交互:展开 FAB 菜单
|
||||
@ -67,9 +70,9 @@ class BaselineProfileGenerator {
|
||||
// 5. 在年视图中滑动到不同年份(覆盖动画和分页路径)
|
||||
val yearGrid = device.findObject(By.res("plus.rua.project:id/year_grid"))
|
||||
if (yearGrid != null) {
|
||||
yearGrid.swipe(300, 600, 300, 1200, 10)
|
||||
yearGrid.swipe(Direction.UP, 0.5f)
|
||||
device.waitForIdle()
|
||||
yearGrid.swipe(300, 1200, 300, 600, 10)
|
||||
yearGrid.swipe(Direction.DOWN, 0.5f)
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
@ -92,17 +95,17 @@ class BaselineProfileGenerator {
|
||||
val calendarArea = device.findObject(By.res("plus.rua.project:id/calendar_pager"))
|
||||
?: device.findObject(By.textContains("2026"))
|
||||
if (calendarArea != null) {
|
||||
calendarArea.swipe(300, 600, 300, 1200, 10)
|
||||
calendarArea.swipe(Direction.UP, 0.5f)
|
||||
device.waitForIdle()
|
||||
calendarArea.swipe(300, 1200, 300, 600, 10)
|
||||
calendarArea.swipe(Direction.DOWN, 0.5f)
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
// 9. 左右滑动切换月份(覆盖 CalendarPager 翻页)
|
||||
if (calendarArea != null) {
|
||||
calendarArea.swipe(600, 800, 100, 800, 10)
|
||||
calendarArea.swipe(Direction.LEFT, 0.5f)
|
||||
device.waitForIdle()
|
||||
calendarArea.swipe(100, 800, 600, 800, 10)
|
||||
calendarArea.swipe(Direction.RIGHT, 0.5f)
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user