perf: 延迟 YearGridView 文本测量到第二帧,补充性能分析文档
YearGridView 首帧 168ms 中约 24ms 来自 remember 同步文本测量。 将 dayLayouts/titleLayouts/weekdayLayouts 改为 produceState, 首帧 Canvas 渲染为空(sharedElement 结构不变),第二帧填充内容。 DEVELOPMENT.md 补充已知性能瓶颈分析和 Baseline Profile 覆盖建议。
This commit is contained in:
parent
ce84c614de
commit
6fac313fdf
@ -27,8 +27,36 @@
|
||||
trace 中包含自定义标记:
|
||||
- `MonthView:Compose` — 月视图重组
|
||||
- `YearView:Compose` — 年视图重组
|
||||
- `YearGridView:*` — 年视图网格组合(首帧耗时关键指标)
|
||||
- `generateMiniMonthDays:*` — 月份网格计算
|
||||
- `VM:collapseProgress` — 折叠动画
|
||||
- `getMonthDays:*` — 月份网格计算
|
||||
|
||||
### 已知性能瓶颈
|
||||
|
||||
#### AnimatedGif 持续解码
|
||||
|
||||
`AnimatedGif` 中的 250×250 WebP 动画在 `repeatCount` 未限制时会以 11-14 FPS 无限循环解码,
|
||||
持续消耗 CPU/GPU。已通过 `ImageOptions { repeatCount(2) }` 限制播放 3 次后停止。
|
||||
|
||||
#### YearGridView 首帧 168ms
|
||||
|
||||
切换到年视图时,`YearGridView` 首次组合需要:
|
||||
- 创建 12 个 MiniMonth composable(含 `sharedElement` + `clickable` + `Canvas` 等 modifier 节点)
|
||||
- 124 次文本测量(93 日期 + 24 标题 + 7 星期)
|
||||
- `SharedTransitionLayout` 注册 12 个共享元素
|
||||
|
||||
文本测量已通过 `produceState` 延迟到第二帧执行,首帧 Canvas 渲染为空。
|
||||
剩余 ~140ms 是 12 个 composable 节点创建 + layout 的 Compose 运行时开销,
|
||||
需要通过 **Baseline Profile** 预编译相关类来优化。
|
||||
|
||||
确保 `macrobenchmark` 的 `BaselineProfileGenerator` 覆盖以下路径:
|
||||
- 冷启动 → FAB → 年视图(触发 YearGridView 首次组合)
|
||||
- 年视图 → 点击任意月份返回月视图(触发 sharedElement 转场)
|
||||
|
||||
#### CalendarPager 预加载
|
||||
|
||||
`beyondViewportPageCount` 已设为 0,避免翻页时预加载相邻月页导致的帧丢失。
|
||||
如需恢复预加载,注意 `compose:lazy:prefetch` 可能产生 400-700ms 卡顿。
|
||||
|
||||
## Baseline Profile
|
||||
|
||||
|
||||
@ -25,7 +25,10 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@ -109,29 +112,24 @@ fun YearGridView(
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
val dayTextStyle = remember { TextStyle(fontSize = 8.sp, lineHeight = 12.sp) }
|
||||
|
||||
// P0-D: 预测量 1..31 × 3 种颜色 = 93 个 TextLayoutResult
|
||||
val dayLayouts = remember(textMeasurer, dayTextStyle, colors) {
|
||||
// 延迟文本测量到下一帧,避免首帧阻塞
|
||||
val dayLayouts by produceState<Map<Pair<Int, Color>, androidx.compose.ui.text.TextLayoutResult>?>(null, textMeasurer, dayTextStyle, colors) {
|
||||
val days = 1..31
|
||||
val colorList = listOf(colors.day, colors.todayText, colors.otherMonth)
|
||||
days.flatMap { d ->
|
||||
value = days.flatMap { d ->
|
||||
colorList.map { c ->
|
||||
(d to c) to textMeasurer.measure(d.toString(), dayTextStyle.copy(color = c))
|
||||
}
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
// P0-H: 预测量月份标题(选中/非选中两种颜色)
|
||||
val titleLayouts = remember(textMeasurer, colors) {
|
||||
(1..12).flatMap { month ->
|
||||
val titleLayouts by produceState<Map<Pair<Int, Boolean>, androidx.compose.ui.text.TextLayoutResult>?>(null, textMeasurer, colors) {
|
||||
value = (1..12).flatMap { month ->
|
||||
val text = "${month}月"
|
||||
listOf(
|
||||
(month to true) to textMeasurer.measure(
|
||||
text,
|
||||
TextStyle(
|
||||
fontSize = 10.sp,
|
||||
color = colors.titleSelected,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
TextStyle(fontSize = 10.sp, color = colors.titleSelected, fontWeight = FontWeight.Bold)
|
||||
),
|
||||
(month to false) to textMeasurer.measure(
|
||||
text,
|
||||
@ -141,9 +139,8 @@ fun YearGridView(
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
// P0-H: 预测量星期标签
|
||||
val weekdayLayouts = remember(textMeasurer, colors) {
|
||||
WEEKDAY_LABELS.associateWith { label ->
|
||||
val weekdayLayouts by produceState<Map<String, androidx.compose.ui.text.TextLayoutResult>?>(null, textMeasurer, colors) {
|
||||
value = WEEKDAY_LABELS.associateWith { label ->
|
||||
textMeasurer.measure(label, TextStyle(fontSize = 8.sp, color = colors.weekday))
|
||||
}
|
||||
}
|
||||
@ -213,9 +210,9 @@ private fun MiniMonth(
|
||||
today: LocalDate,
|
||||
days: List<MiniDayData>,
|
||||
colors: MiniMonthColors,
|
||||
dayLayouts: Map<Pair<Int, Color>, androidx.compose.ui.text.TextLayoutResult>,
|
||||
titleLayouts: Map<Pair<Int, Boolean>, androidx.compose.ui.text.TextLayoutResult>,
|
||||
weekdayLayouts: Map<String, androidx.compose.ui.text.TextLayoutResult>,
|
||||
dayLayouts: Map<Pair<Int, Color>, androidx.compose.ui.text.TextLayoutResult>?,
|
||||
titleLayouts: Map<Pair<Int, Boolean>, androidx.compose.ui.text.TextLayoutResult>?,
|
||||
weekdayLayouts: Map<String, androidx.compose.ui.text.TextLayoutResult>?,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
@ -240,10 +237,13 @@ private fun MiniMonth(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Canvas(modifier = Modifier.fillMaxWidth().height(totalHeight)) {
|
||||
val dl = dayLayouts ?: return@Canvas
|
||||
val tl = titleLayouts ?: return@Canvas
|
||||
val wl = weekdayLayouts ?: return@Canvas
|
||||
val cellWidth = size.width / 7f
|
||||
|
||||
// 1. 绘制标题
|
||||
val titleLayout = titleLayouts[month to isSelected]!!
|
||||
val titleLayout = tl[month to isSelected]!!
|
||||
drawText(
|
||||
textLayoutResult = titleLayout,
|
||||
topLeft = Offset(
|
||||
@ -255,7 +255,7 @@ private fun MiniMonth(
|
||||
// 2. 绘制星期行
|
||||
val weekdayY = titleHeightPx + titleToWeekdayGapPx
|
||||
WEEKDAY_LABELS.forEachIndexed { i, label ->
|
||||
val layout = weekdayLayouts[label]!!
|
||||
val layout = wl[label]!!
|
||||
drawText(
|
||||
textLayoutResult = layout,
|
||||
topLeft = Offset(
|
||||
@ -292,7 +292,7 @@ private fun MiniMonth(
|
||||
}
|
||||
|
||||
if (dayNum > 0) {
|
||||
dayLayouts[dayNum to textColor]?.let { layoutResult ->
|
||||
dl[dayNum to textColor]?.let { layoutResult ->
|
||||
drawText(
|
||||
textLayoutResult = layoutResult,
|
||||
topLeft = Offset(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user