perf: 添加性能追踪标记并改进基线配置文件生成器
This commit is contained in:
parent
4de00e35dc
commit
281abcf66b
@ -27,10 +27,17 @@
|
||||
trace 中包含自定义标记:
|
||||
|
||||
- `MonthView:Compose` — 月视图重组
|
||||
- `CalendarPagerArea` — 日历分页器区域
|
||||
- `CalendarPager:Page:*` — 月视图单页重组
|
||||
- `CalendarMonthPage:*` — 月页面数据计算(含折叠动画准备)
|
||||
- `WeekPager:Page` — 周视图单页重组
|
||||
- `YearView:Compose` — 年视图重组
|
||||
- `YearGridView:*` — 年视图网格组合(首帧耗时关键指标)
|
||||
- `generateMiniMonthDays:*` — 月份网格计算
|
||||
- `VM:collapseProgress` — 折叠动画
|
||||
- `MonthView→YearView` / `YearView→MonthView` — 视图切换
|
||||
- `YearView:SelectMonth` — 年视图选月
|
||||
- `getMonthDays:*` — ViewModel 月份网格计算
|
||||
- `VM:collapseProgress:*` — 折叠动画拖拽(onDrag/onDragEnd/onExpandDrag/onExpandDragEnd)
|
||||
|
||||
## Baseline Profile
|
||||
|
||||
|
||||
@ -18617,4 +18617,17 @@ SPLplus/rua/project/ui/WeekdayHeaderKt;->WeekdayHeader$lambda$0$0(Landroidx/comp
|
||||
SPLplus/rua/project/ui/WeekdayHeaderKt;->WeekdayHeader(Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
|
||||
Lplus/rua/project/ui/WeekdayHeaderKt$$ExternalSyntheticLambda0;
|
||||
SPLplus/rua/project/ui/WeekdayHeaderKt$$ExternalSyntheticLambda0;-><init>()V
|
||||
SPLplus/rua/project/ui/WeekdayHeaderKt$$ExternalSyntheticLambda0;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
SPLplus/rua/project/ui/WeekdayHeaderKt$$ExternalSyntheticLambda0;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
Lplus/rua/project/ui/YearGridViewKt;
|
||||
HPLplus/rua/project/ui/YearGridViewKt;->YearGridView(IILkotlinx/datetime/LocalDate;Lkotlin/jvm/functions/Function1;Landroidx/compose/animation/SharedTransitionScope;Landroidx/compose/animation/AnimatedVisibilityScope;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
|
||||
HPLplus/rua/project/ui/YearGridViewKt;->generateMiniMonthDays(II)Ljava/util/List;
|
||||
Lplus/rua/project/ui/YearGridViewKt;->MiniMonth(IIZLkotlinx/datetime/LocalDate;Ljava/util/List;Lplus/rua/project/ui/MiniMonthColors;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
|
||||
Lplus/rua/project/ui/YearGridViewKt;->YearHeader(IILkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
|
||||
Lplus/rua/project/ui/MiniMonthColors;
|
||||
HPLplus/rua/project/CalendarViewModel;->toggleYearView()V
|
||||
HPLplus/rua/project/CalendarViewModel;->selectMonthFromYearView(I)V
|
||||
HPLplus/rua/project/CalendarViewModel;->onDrag(F)V
|
||||
HPLplus/rua/project/CalendarViewModel;->onDragEnd()V
|
||||
HPLplus/rua/project/CalendarViewModel;->onExpandDrag(F)V
|
||||
HPLplus/rua/project/CalendarViewModel;->onExpandDragEnd()V
|
||||
HPLplus/rua/project/CalendarViewModel;->getMonthDays(II)Ljava/util/List;
|
||||
|
||||
@ -280,7 +280,9 @@ class CalendarViewModel(
|
||||
* @param delta 拖拽增量,已归一化到 [0,1] 区间
|
||||
*/
|
||||
fun onDrag(delta: Float) {
|
||||
composeTraceBeginSection("VM:collapseProgress:onDrag")
|
||||
_collapseProgress.value = (_collapseProgress.value + delta).coerceIn(0f, 1f)
|
||||
composeTraceEndSection()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -289,6 +291,7 @@ class CalendarViewModel(
|
||||
* 拖拽超过阈值时自动折叠到周视图,否则回弹到月视图。
|
||||
*/
|
||||
fun onDragEnd() {
|
||||
composeTraceBeginSection("VM:collapseProgress:onDragEnd")
|
||||
val progress = _collapseProgress.value
|
||||
if (progress > COLLAPSE_THRESHOLD) {
|
||||
_isCollapsed.value = true
|
||||
@ -297,6 +300,7 @@ class CalendarViewModel(
|
||||
_isCollapsed.value = false
|
||||
_collapseProgress.value = 0f
|
||||
}
|
||||
composeTraceEndSection()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -305,9 +309,11 @@ class CalendarViewModel(
|
||||
* @param delta 拖拽增量,已归一化到 [0,1] 区间
|
||||
*/
|
||||
fun onExpandDrag(delta: Float) {
|
||||
composeTraceBeginSection("VM:collapseProgress:onExpandDrag")
|
||||
val old = _collapseProgress.value
|
||||
_collapseProgress.value = (_collapseProgress.value + delta).coerceIn(0f, 1f)
|
||||
logd(TAG_VM, "onExpandDrag: delta=$delta old=$old new=${_collapseProgress.value}")
|
||||
composeTraceEndSection()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -316,6 +322,7 @@ class CalendarViewModel(
|
||||
* 下拉超过阈值时自动展开到月视图,否则回弹到周视图。
|
||||
*/
|
||||
fun onExpandDragEnd() {
|
||||
composeTraceBeginSection("VM:collapseProgress:onExpandDragEnd")
|
||||
val progress = _collapseProgress.value
|
||||
val result = if (progress < (1 - COLLAPSE_THRESHOLD)) {
|
||||
_isCollapsed.value = false
|
||||
@ -327,6 +334,7 @@ class CalendarViewModel(
|
||||
"COLLAPSED (bounce back)"
|
||||
}
|
||||
logd(TAG_VM, "onExpandDragEnd: progress=$progress threshold=${1 - COLLAPSE_THRESHOLD} result=$result")
|
||||
composeTraceEndSection()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -21,12 +21,10 @@ import plus.rua.project.getWebpUri
|
||||
*/
|
||||
private val WEBP_FILES = (1..152).map { "${it.toString().padStart(3, '0')}.webp" }
|
||||
|
||||
private const val REPEAT_COUNT = 2
|
||||
|
||||
/**
|
||||
* 显示动画 WebP 图片,切换日期时随机选择一个。
|
||||
*
|
||||
* 动画播放 3 次(1 + [REPEAT_COUNT])后停止,避免持续解码导致的帧丢失。
|
||||
* 动画无限循环播放。
|
||||
*
|
||||
* @param modifier 应用于图片的 Modifier
|
||||
* @param contentDescription 无障碍描述
|
||||
@ -55,7 +53,7 @@ fun AnimatedGif(
|
||||
}
|
||||
|
||||
val state = rememberAsyncImageState(
|
||||
options = remember { ImageOptions { repeatCount(REPEAT_COUNT) } }
|
||||
options = remember { ImageOptions { repeatCount(-1) } }
|
||||
)
|
||||
|
||||
AsyncImage(
|
||||
|
||||
@ -21,6 +21,8 @@ import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import plus.rua.project.composeTraceBeginSection
|
||||
import plus.rua.project.composeTraceEndSection
|
||||
import plus.rua.project.util.logd
|
||||
import kotlinx.datetime.DatePeriod
|
||||
import kotlinx.datetime.LocalDate
|
||||
@ -68,6 +70,7 @@ fun CalendarMonthPage(
|
||||
onRowHeightMeasured: ((Int) -> Unit)? = null,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
composeTraceBeginSection("CalendarMonthPage:$year-$month")
|
||||
val days = remember(year, month) {
|
||||
generateMonthDays(year, month)
|
||||
}
|
||||
@ -129,6 +132,7 @@ fun CalendarMonthPage(
|
||||
else Modifier
|
||||
)
|
||||
) {
|
||||
composeTraceEndSection()
|
||||
weeks.forEachIndexed { weekIndex, week ->
|
||||
key(weekIndex) {
|
||||
WeekRow(
|
||||
|
||||
@ -15,6 +15,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import plus.rua.project.composeTraceBeginSection
|
||||
import plus.rua.project.composeTraceEndSection
|
||||
import plus.rua.project.util.logd
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import kotlinx.coroutines.flow.drop
|
||||
@ -105,6 +107,7 @@ fun CalendarPager(
|
||||
if (isCurrentPage) {
|
||||
logd("AnimLog", "[CalendarPager] Compose page=$page ($year-$month) alpha=$alpha pageOffset=$pageOffset")
|
||||
}
|
||||
composeTraceBeginSection("CalendarPager:Page:$year-$month")
|
||||
CalendarMonthPage(
|
||||
year = year,
|
||||
month = month,
|
||||
@ -137,5 +140,6 @@ fun CalendarPager(
|
||||
onRowHeightMeasured = onRowHeightMeasured,
|
||||
modifier = Modifier.alpha(alpha)
|
||||
)
|
||||
composeTraceEndSection()
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,13 @@
|
||||
package plus.rua.project.baseline
|
||||
|
||||
import android.util.Log
|
||||
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.Assert.assertNotNull
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@ -44,95 +47,95 @@ class BaselineProfileGenerator {
|
||||
packageName = "plus.rua.project",
|
||||
includeInStartupProfile = true,
|
||||
profileBlock = {
|
||||
val TAG = "BaselineProfile"
|
||||
|
||||
// 1. 冷启动:从 launcher 启动应用
|
||||
// 注:使用 shell command 绕过 startActivityAndWait,因为模拟器的 software
|
||||
// renderer 不支持 gfxinfo framestats,会导致 amStartAndWait 超时。
|
||||
pressHome()
|
||||
device.executeShellCommand(
|
||||
"am start -W -n plus.rua.project/.MainActivity"
|
||||
)
|
||||
device.waitForIdle()
|
||||
|
||||
// 3. 模拟用户交互:展开 FAB 菜单
|
||||
// 2. 展开 FAB 菜单,等待菜单项出现
|
||||
val fab = device.findObject(By.res("plus.rua.project:id/fab_menu"))
|
||||
if (fab != null) {
|
||||
fab.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
assertNotNull("FAB 按钮必须存在", fab)
|
||||
fab!!.click()
|
||||
val yearViewItem = device.wait(Until.findObject(By.text("年视图")), 3000)
|
||||
Log.d(TAG, "FAB 菜单展开: yearViewItem=${yearViewItem != null}")
|
||||
|
||||
// 4. 切换到年视图(覆盖 YearGridView、YearHeader、MiniMonth 路径)
|
||||
val yearViewButton = device.findObject(By.text("年视图"))
|
||||
if (yearViewButton != null) {
|
||||
yearViewButton.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
// 3. 切换到年视图(覆盖 YearGridView、YearHeader、MiniMonth 路径)
|
||||
assertNotNull("年视图菜单项必须出现", yearViewItem)
|
||||
yearViewItem!!.click()
|
||||
val yearGrid = device.wait(Until.findObject(By.res("plus.rua.project:id/year_grid")), 3000)
|
||||
Log.d(TAG, "年视图加载: yearGrid=${yearGrid != null}")
|
||||
assertNotNull("YearGridView 必须加载", yearGrid)
|
||||
device.waitForIdle()
|
||||
|
||||
// 5. 在年视图中滑动到不同年份(覆盖动画和分页路径)
|
||||
val yearGrid = device.findObject(By.res("plus.rua.project:id/year_grid"))
|
||||
if (yearGrid != null) {
|
||||
yearGrid.swipe(Direction.UP, 0.5f)
|
||||
device.waitForIdle()
|
||||
yearGrid.swipe(Direction.DOWN, 0.5f)
|
||||
device.waitForIdle()
|
||||
}
|
||||
// 4. 在年视图中滑动到不同年份(覆盖动画和分页路径)
|
||||
yearGrid!!.swipe(Direction.UP, 0.5f)
|
||||
device.waitForIdle()
|
||||
yearGrid.swipe(Direction.DOWN, 0.5f)
|
||||
device.waitForIdle()
|
||||
|
||||
// 6. 切换回月视图
|
||||
val monthViewButton = device.findObject(By.text("月视图"))
|
||||
if (monthViewButton != null) {
|
||||
monthViewButton.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
// 5. 展开 FAB 并切换回月视图
|
||||
val fabForMonth = device.findObject(By.res("plus.rua.project:id/fab_menu"))
|
||||
assertNotNull("FAB 按钮必须存在(返回月视图)", fabForMonth)
|
||||
fabForMonth!!.click()
|
||||
val monthViewItem = device.wait(Until.findObject(By.text("月视图")), 3000)
|
||||
Log.d(TAG, "FAB 菜单展开: monthViewItem=${monthViewItem != null}")
|
||||
assertNotNull("月视图菜单项必须出现", monthViewItem)
|
||||
monthViewItem!!.click()
|
||||
device.waitForIdle()
|
||||
|
||||
// 7. 点击某一天(覆盖 DayCell 点击路径 + 底部卡片展开)
|
||||
// 6. 点击某一天(覆盖 DayCell 点击路径 + 底部卡片展开)
|
||||
val todayCell = device.findObject(By.descContains("今天"))
|
||||
?: device.findObject(By.text("21"))
|
||||
if (todayCell != null) {
|
||||
todayCell.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
assertNotNull("DayCell 必须可点击", todayCell)
|
||||
todayCell!!.click()
|
||||
device.waitForIdle()
|
||||
|
||||
// 8. 拖拽 BottomCard 触发月视图↔周视图折叠/展开
|
||||
// 7. 拖拽 BottomCard 触发月视图↔周视图折叠/展开
|
||||
val bottomCard = device.findObject(By.res("plus.rua.project:id/bottom_card"))
|
||||
if (bottomCard != null) {
|
||||
val bounds = bottomCard.visibleBounds
|
||||
val centerX = bounds.centerX()
|
||||
val centerY = bounds.centerY()
|
||||
val dragDistance = (bounds.height() * 0.4).toInt()
|
||||
// 向上拖拽 → 折叠到周视图
|
||||
device.drag(centerX, centerY, centerX, centerY - dragDistance, 20)
|
||||
device.waitForIdle()
|
||||
// 向下拖拽 → 展开到月视图
|
||||
device.drag(centerX, centerY - dragDistance, centerX, centerY, 20)
|
||||
device.waitForIdle()
|
||||
}
|
||||
assertNotNull("BottomCard 必须存在", bottomCard)
|
||||
val bounds = bottomCard!!.visibleBounds
|
||||
val centerX = bounds.centerX()
|
||||
val centerY = bounds.centerY()
|
||||
val dragDistance = (bounds.height() * 0.4).toInt()
|
||||
// 向上拖拽 → 折叠到周视图
|
||||
device.drag(centerX, centerY, centerX, centerY - dragDistance, 20)
|
||||
device.waitForIdle()
|
||||
// 向下拖拽 → 展开到月视图
|
||||
device.drag(centerX, centerY - dragDistance, centerX, centerY, 20)
|
||||
device.waitForIdle()
|
||||
|
||||
// 9. 展开 FAB 并进入工具页面
|
||||
val fabMenu = device.findObject(By.res("plus.rua.project:id/fab_menu"))
|
||||
if (fabMenu != null) {
|
||||
fabMenu.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
val toolsButton = device.findObject(By.text("工具"))
|
||||
if (toolsButton != null) {
|
||||
toolsButton.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
// 8. 展开 FAB 并进入工具页面
|
||||
val fabForTools = device.findObject(By.res("plus.rua.project:id/fab_menu"))
|
||||
assertNotNull("FAB 按钮必须存在(工具页)", fabForTools)
|
||||
fabForTools!!.click()
|
||||
val toolsButton = device.wait(Until.findObject(By.text("工具")), 3000)
|
||||
Log.d(TAG, "FAB 菜单展开: toolsButton=${toolsButton != null}")
|
||||
assertNotNull("工具菜单项必须出现", toolsButton)
|
||||
toolsButton!!.click()
|
||||
device.waitForIdle()
|
||||
|
||||
// 10. 进入日期检查器(覆盖 DateCheckerScreen)
|
||||
val dateCheckerEntry = device.findObject(By.res("plus.rua.project:id/tool_date_checker"))
|
||||
if (dateCheckerEntry != null) {
|
||||
dateCheckerEntry.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
// 9. 进入日期检查器(覆盖 DateCheckerScreen)
|
||||
val dateCheckerEntry = device.wait(
|
||||
Until.findObject(By.res("plus.rua.project:id/tool_date_checker")), 3000
|
||||
)
|
||||
assertNotNull("日期检查器入口必须存在", dateCheckerEntry)
|
||||
dateCheckerEntry!!.click()
|
||||
device.waitForIdle()
|
||||
|
||||
// 11. 点击日历图标打开 DatePickerDialog(覆盖 DatePicker)
|
||||
val datePickerBtn = device.findObject(By.res("plus.rua.project:id/date_picker_button"))
|
||||
// 10. 点击日历图标打开 DatePickerDialog(覆盖 DatePicker)
|
||||
val datePickerBtn = device.wait(
|
||||
Until.findObject(By.res("plus.rua.project:id/date_picker_button")), 3000
|
||||
)
|
||||
if (datePickerBtn != null) {
|
||||
datePickerBtn.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
// 12. 等待 DatePickerDialog 并点击确定
|
||||
// 11. 等待 DatePickerDialog 并点击确定
|
||||
device.wait(Until.findObject(By.text("确定")), 2000)
|
||||
val confirmBtn = device.findObject(By.text("确定"))
|
||||
if (confirmBtn != null) {
|
||||
@ -140,54 +143,56 @@ class BaselineProfileGenerator {
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
// 13. 点击 FAB 添加新行(覆盖 FAB + LazyColumn items 重组)
|
||||
// 12. 点击 FAB 添加新行(覆盖 FAB + LazyColumn items 重组)
|
||||
val dateCheckerFab = device.findObject(By.res("plus.rua.project:id/date_checker_fab"))
|
||||
if (dateCheckerFab != null) {
|
||||
dateCheckerFab.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
// 14. 返回工具页
|
||||
device.pressBack()
|
||||
device.waitForIdle()
|
||||
|
||||
// 15. 返回主界面
|
||||
device.pressBack()
|
||||
device.waitForIdle()
|
||||
|
||||
// 16. 左右滑动切换月份(覆盖 CalendarPager 翻页)
|
||||
val calendarPager = device.findObject(By.res("plus.rua.project:id/calendar_pager"))
|
||||
if (calendarPager != null) {
|
||||
calendarPager.swipe(Direction.LEFT, 0.5f)
|
||||
device.waitForIdle()
|
||||
calendarPager.swipe(Direction.RIGHT, 0.5f)
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
// 17. 进入关于页面(覆盖 AboutScreen + AnimatedGif)
|
||||
val aboutButton = device.findObject(By.text("关于"))
|
||||
if (aboutButton != null) {
|
||||
aboutButton.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
// 11. 进入开源许可页面(覆盖 LicensesScreen)
|
||||
val licensesButton = device.findObject(By.text("开源许可"))
|
||||
if (licensesButton != null) {
|
||||
licensesButton.click()
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
// 12. 等待许可列表加载
|
||||
device.wait(Until.findObject(By.textContains("Apache")), 2000)
|
||||
|
||||
// 13. 返回关于页
|
||||
// 13. 返回工具页
|
||||
device.pressBack()
|
||||
device.waitForIdle()
|
||||
|
||||
// 14. 返回主界面
|
||||
device.pressBack()
|
||||
device.waitForIdle()
|
||||
|
||||
// 15. 左右滑动切换月份(覆盖 CalendarPager 翻页)
|
||||
val calendarPager = device.findObject(By.res("plus.rua.project:id/calendar_pager"))
|
||||
assertNotNull("CalendarPager 必须存在", calendarPager)
|
||||
calendarPager!!.swipe(Direction.LEFT, 0.5f)
|
||||
device.waitForIdle()
|
||||
calendarPager.swipe(Direction.RIGHT, 0.5f)
|
||||
device.waitForIdle()
|
||||
|
||||
// 16. 进入关于页面(覆盖 AboutScreen + AnimatedGif)
|
||||
val fabForAbout = device.findObject(By.res("plus.rua.project:id/fab_menu"))
|
||||
assertNotNull("FAB 按钮必须存在(关于页)", fabForAbout)
|
||||
fabForAbout!!.click()
|
||||
val aboutButton = device.wait(Until.findObject(By.text("关于")), 3000)
|
||||
assertNotNull("关于菜单项必须出现", aboutButton)
|
||||
aboutButton!!.click()
|
||||
device.waitForIdle()
|
||||
|
||||
// 17. 进入开源许可页面(覆盖 LicensesScreen)
|
||||
val licensesButton = device.wait(Until.findObject(By.text("开源许可")), 3000)
|
||||
assertNotNull("开源许可按钮必须存在", licensesButton)
|
||||
licensesButton!!.click()
|
||||
device.waitForIdle()
|
||||
|
||||
// 18. 等待许可列表加载
|
||||
device.wait(Until.findObject(By.textContains("Apache")), 2000)
|
||||
|
||||
// 19. 返回关于页
|
||||
device.pressBack()
|
||||
device.waitForIdle()
|
||||
|
||||
// 20. 返回主界面
|
||||
device.pressBack()
|
||||
device.waitForIdle()
|
||||
|
||||
Log.d(TAG, "Baseline profile 生成完成,所有路径已覆盖")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user