perf: 集成 Baseline Profiles,优化启动性能与文档
- 添加 core/proguard-rules.pro 保留关键类方法签名 - 添加 core/src/main/baseline-prof.txt 预编译热点路径 - 引入 androidx.profileinstaller 依赖 - LunarCache 新增 formatLunarDate() 复用缓存数据 - BottomCard 使用 remember 缓存农历描述避免重复计算 - CalendarMonthView 延迟一帧显示 BottomCard,避免首帧阻塞 - README 补充 Baseline Profiles 维护指南与性能监控说明 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6aefaf33a6
commit
6582d5970e
112
README.md
112
README.md
@ -20,3 +20,115 @@
|
||||
- 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
|
||||
```
|
||||
|
||||
## Baseline Profiles 维护指南
|
||||
|
||||
本项目已集成 [Baseline Profiles](https://developer.android.com/topic/performance/baselineprofiles),用于消除冷启动时的 JIT 编译开销。Release APK 构建时会自动将 `core/src/main/baseline-prof.txt` 打包进 `assets/dexopt/baseline.prof`。
|
||||
|
||||
### 何时需要更新 `baseline-prof.txt`
|
||||
|
||||
每次发版前,检查以下清单。任一条件命中,就需要更新 profile:
|
||||
|
||||
| 检查项 | 是否需要更新 | 说明 |
|
||||
|--------|-------------|------|
|
||||
| 新增/修改了首帧渲染的 composable | 必须 | `MainActivity` → `CalendarMonthView` 启动路径上的任何 composable |
|
||||
| 修改了 `DayCell.kt` 的方法签名 | 必须 | DayCell 是启动最热点(35 次调用) |
|
||||
| 修改了 `CalendarMonthPage.kt` 的方法签名 | 必须 | 月度网格页面在首帧渲染 |
|
||||
| 修改了 `CalendarMonthView.kt` 的方法签名 | 必须 | 根 composable |
|
||||
| 修改了 `LunarCache.kt` 的计算逻辑 | 建议 | 缓存 miss 时会走 compute() 路径 |
|
||||
| 新增/删除了 `tyme4kt` 的调用 | 建议 | 农历计算是 CPU 密集型 |
|
||||
| 仅修改 UI 颜色、文字、布局 | 不需要 | 不涉及方法签名变化 |
|
||||
| 新增设置页、关于页等非首屏页面 | 不需要 | 不在冷启动路径 |
|
||||
|
||||
### 如何更新
|
||||
|
||||
1. **定位新增/变更的热点方法**
|
||||
|
||||
通过 logcat 抓取启动时的 JIT 编译日志:
|
||||
```bash
|
||||
adb shell setprop dalvik.vm.extra-opts -verbose:compiler
|
||||
adb logcat -s "JIT" | grep "plus/rua/project"
|
||||
```
|
||||
|
||||
或抓取带 ART 详细日志的启动 trace:
|
||||
```bash
|
||||
adb shell am start -W -n plus.rua.project/.MainActivity
|
||||
adb logcat -d | grep -E "JIT|dex2oat|BaselineProfile"
|
||||
```
|
||||
|
||||
2. **编辑 `core/src/main/baseline-prof.txt`**
|
||||
|
||||
新增规则格式:
|
||||
```
|
||||
HSPL<类全路径;-><方法名>(<参数类型签名>)<返回类型签名>
|
||||
```
|
||||
|
||||
例如新增一个 composable `NewWidget`:
|
||||
```
|
||||
HSPLplus/rua/project/ui/NewWidgetKt;->NewWidget(Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;I)V
|
||||
```
|
||||
|
||||
常用签名速查:
|
||||
|
||||
| Kotlin 类型 | Profile 签名 |
|
||||
|------------|-------------|
|
||||
| `Int` | `I` |
|
||||
| `Float` | `F` |
|
||||
| `Boolean` | `Z` |
|
||||
| `String` | `Ljava/lang/String;` |
|
||||
| `LocalDate` | `Lkotlinx/datetime/LocalDate;` |
|
||||
| `Modifier` | `Landroidx/compose/ui/Modifier;` |
|
||||
| `Composer` | `Landroidx/compose/runtime/Composer;` |
|
||||
| `Function0<Unit>` | `Lkotlin/jvm/functions/Function0;` |
|
||||
| `Function1<T, R>` | `Lkotlin/jvm/functions/Function1;` |
|
||||
| `ShiftKind?` | `Lplus/rua/project/ShiftKind;` |
|
||||
| `Unit` / 无返回值 | `V` |
|
||||
|
||||
3. **验证方法名不被混淆**
|
||||
|
||||
如果新增的方法在 `core/proguard-rules.pro` 中没有保留规则,添加:
|
||||
```proguard
|
||||
-keepclassmembers class plus.rua.project.ui.NewWidgetKt {
|
||||
public static void NewWidget(...);
|
||||
}
|
||||
```
|
||||
|
||||
4. **编译验证**
|
||||
```bash
|
||||
./gradlew :core:compileDebugKotlin
|
||||
./gradlew :app:assembleRelease
|
||||
```
|
||||
|
||||
5. **确认 profile 打包成功**
|
||||
```bash
|
||||
unzip -l app/build/outputs/apk/release/app-release-unsigned.apk | grep baseline
|
||||
# 应看到 assets/dexopt/baseline.prof
|
||||
```
|
||||
|
||||
### 自动化替代方案(推荐后期迁移)
|
||||
|
||||
手动维护容易遗漏,长期建议迁移到 **Macrobenchmark 自动生成**:
|
||||
|
||||
1. 创建 `:macrobenchmark` 模块
|
||||
2. 编写启动基准测试(自动遍历冷启动路径)
|
||||
3. `./gradlew :macrobenchmark:connectedBenchmarkAndroidTest`
|
||||
4. 自动输出 `baseline-prof.txt`,直接替换即可
|
||||
|
||||
参考:[Android Baseline Profiles 官方文档](https://developer.android.com/topic/performance/baselineprofiles/create-profile)
|
||||
|
||||
## 性能监控
|
||||
|
||||
项目内置了 `LunarCache`(农历/节气/节假日缓存)和性能追踪(`ComposeTrace.kt`)。查看 `DEVELOPMENT.md` 了解如何使用 Perfetto/Systrace 进行深度性能分析。
|
||||
|
||||
@ -11,6 +11,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||
consumerProguardFiles("proguard-rules.pro")
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
@ -55,6 +56,7 @@ dependencies {
|
||||
implementation(libs.tyme4kt)
|
||||
implementation(libs.sketch.compose)
|
||||
implementation(libs.sketch.animated.gif)
|
||||
implementation(libs.androidx.profileinstaller)
|
||||
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:${libs.versions.kotlin.get()}")
|
||||
testImplementation(libs.kotlinx.coroutines.test)
|
||||
|
||||
17
core/proguard-rules.pro
vendored
Normal file
17
core/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Baseline Profiles 保留规则:确保方法名不被 R8 混淆,使 profile 规则匹配正确
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# 保留 YaYa 业务类方法名(profile 中引用的类)
|
||||
-keepclassmembers class plus.rua.project.ui.DayCellKt {
|
||||
public static void DayCell(...);
|
||||
}
|
||||
-keepclassmembers class plus.rua.project.LunarCache {
|
||||
public static plus.rua.project.DayCellInfo getOrCompute(kotlinx.datetime.LocalDate);
|
||||
public static java.lang.String formatLunarDate(kotlinx.datetime.LocalDate);
|
||||
}
|
||||
-keepclassmembers class plus.rua.project.DayCellInfo {
|
||||
public java.lang.String getAnnotationText();
|
||||
public boolean getIsAnnotationHighlight();
|
||||
public java.lang.String getHolidayBadge();
|
||||
public java.lang.String getLunarMonthName();
|
||||
}
|
||||
142
core/src/main/baseline-prof.txt
Normal file
142
core/src/main/baseline-prof.txt
Normal file
@ -0,0 +1,142 @@
|
||||
# YaYa 日历应用 Baseline Profile
|
||||
# 覆盖冷启动热点路径,消除 JIT 编译导致的启动掉帧
|
||||
|
||||
# ========== 业务层热点方法 ==========
|
||||
|
||||
# DayCell — 启动时最热的 composable(35 次调用)
|
||||
HSPLplus/rua/project/ui/DayCellKt;->DayCell(Lkotlinx/datetime/LocalDate;ZZZLplus/rua/project/ShiftKind;ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;)V
|
||||
|
||||
# LunarCache — 日期计算缓存
|
||||
HSPLplus/rua/project/LunarCache;->getOrCompute(Lkotlinx/datetime/LocalDate;)Lplus/rua/project/DayCellInfo;
|
||||
HSPLplus/rua/project/LunarCache;->compute(Lkotlinx/datetime/LocalDate;)Lplus/rua/project/DayCellInfo;
|
||||
|
||||
# DayCellInfo 数据类
|
||||
HSPLplus/rua/project/DayCellInfo;-><init>(Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V
|
||||
HSPLplus/rua/project/DayCellInfo;->getAnnotationText()Ljava/lang/String;
|
||||
HSPLplus/rua/project/DayCellInfo;->getIsAnnotationHighlight()Z
|
||||
HSPLplus/rua/project/DayCellInfo;->getHolidayBadge()Ljava/lang/String;
|
||||
|
||||
# CalendarMonthPage — 月度网格页面
|
||||
HSPLplus/rua/project/ui/CalendarMonthPageKt;->CalendarMonthPage(ILkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlin/jvm/functions/Function1;FIFLkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;)V
|
||||
HSPLplus/rua/project/ui/CalendarMonthPageKt;->generateMonthDays(II)Ljava/util/List;
|
||||
|
||||
# CalendarMonthView — 根 composable
|
||||
HSPLplus/rua/project/ui/CalendarMonthViewKt;->CalendarMonthView(Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;)V
|
||||
HSPLplus/rua/project/ui/CalendarMonthViewKt;->access$uiPerfLog(Ljava/lang/String;Ljava/lang/String;)V
|
||||
|
||||
# CalendarPager — 分页器
|
||||
HSPLplus/rua/project/ui/CalendarPagerKt;->CalendarPager(Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;FIFLkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function1;Landroidx/compose/foundation/pager/PagerState;Landroidx/compose/ui/Modifier;)V
|
||||
|
||||
# WeekPager — 周视图分页器
|
||||
HSPLplus/rua/project/ui/WeekPagerKt;->WeekPager(Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ZLandroidx/compose/ui/Modifier;)V
|
||||
|
||||
# BottomCard — 底部卡片
|
||||
HSPLplus/rua/project/ui/BottomCardKt;->BottomCard(Lplus/rua/project/CalendarViewModel;Lkotlinx/datetime/LocalDate;Lkotlinx/datetime/LocalDate;FLandroidx/compose/ui/Modifier;)V
|
||||
HSPLplus/rua/project/ui/BottomCardKt;->access$formatLunarDate(Lkotlinx/datetime/LocalDate;)Ljava/lang/String;
|
||||
|
||||
# CalendarViewModel — 状态管理
|
||||
HSPLplus/rua/project/CalendarViewModel;-><init>(Lkotlinx/coroutines/CoroutineScope;Lkotlin/time/Clock;)V
|
||||
HSPLplus/rua/project/CalendarViewModel;->getSelectedDate()Lkotlinx/datetime/LocalDate;
|
||||
HSPLplus/rua/project/CalendarViewModel;->getMonthDays(II)Ljava/util/List;
|
||||
|
||||
# CalendarUtils — 工具函数
|
||||
HSPLplus/rua/project/ui/CalendarUtilsKt;->calculateWeeksCount(II)I
|
||||
HSPLplus/rua/project/ui/CalendarUtilsKt;->calculateWeeksCountForPage(ILkotlinx/datetime/LocalDate;)I
|
||||
HSPLplus/rua/project/ui/CalendarUtilsKt;->pageToYearMonth(IILkotlinx/datetime/LocalDate;)Lkotlin/Pair;
|
||||
HSPLplus/rua/project/ui/CalendarUtilsKt;->yearMonthToPage(IIII)I
|
||||
|
||||
# ========== Compose 框架关键路径 ==========
|
||||
|
||||
# Composer — Compose 运行时核心
|
||||
HSPLandroidx/compose/runtime/ComposerImpl;->startRestartGroup(I)Landroidx/compose/runtime/Composer;
|
||||
HSPLandroidx/compose/runtime/ComposerImpl;->endRestartGroup()Landroidx/compose/runtime/ScopeUpdateScope;
|
||||
HSPLandroidx/compose/runtime/ComposerImpl;->startReplaceableGroup(I)V
|
||||
HSPLandroidx/compose/runtime/ComposerImpl;->endReplaceableGroup()V
|
||||
HSPLandroidx/compose/runtime/ComposerImpl;->changed(Ljava/lang/Object;)Z
|
||||
HSPLandroidx/compose/runtime/ComposerImpl;->rememberedValue()Ljava/lang/Object;
|
||||
HSPLandroidx/compose/runtime/ComposerImpl;->updateRememberedValue(Ljava/lang/Object;)V
|
||||
|
||||
# Recomposer
|
||||
HSPLandroidx/compose/runtime/Recomposer;->composeInitial$runtime_release(Landroidx/compose/runtime/ControlledComposition;Lkotlin/jvm/functions/Function2;)V
|
||||
HSPLandroidx/compose/runtime/Recomposer;->runRecomposeAndApplyChanges(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
|
||||
# AndroidComposeView — 渲染
|
||||
HSPLandroidx/compose/ui/platform/AndroidComposeView;->draw(Landroid/graphics/Canvas;)V
|
||||
HSPLandroidx/compose/ui/platform/AndroidComposeView;->onMeasure(II)V
|
||||
HSPLandroidx/compose/ui/platform/AndroidComposeView;->onLayout(ZIIII)V
|
||||
HSPLandroidx/compose/ui/platform/AndroidComposeView;->dispatchDraw(Landroid/graphics/Canvas;)V
|
||||
|
||||
# remember / derivedStateOf
|
||||
HSPLandroidx/compose/runtime/EffectsKt;->remember(Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
|
||||
HSPLandroidx/compose/runtime/EffectsKt;->remember([Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object;
|
||||
HSPLandroidx/compose/runtime/SnapshotStateKt;->derivedStateOf(Lkotlin/jvm/functions/Function0;)Landroidx/compose/runtime/State;
|
||||
|
||||
# SideEffect / LaunchedEffect
|
||||
HSPLandroidx/compose/runtime/EffectsKt;->SideEffect(Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V
|
||||
HSPLandroidx/compose/runtime/EffectsKt;->LaunchedEffect(Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V
|
||||
|
||||
# ========== Compose UI 基础组件 ==========
|
||||
|
||||
# Box / Column / Row
|
||||
HSPLandroidx/compose/foundation/layout/BoxKt;->Box(Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;ZLkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
|
||||
HSPLandroidx/compose/foundation/layout/ColumnKt;->Column(Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/layout/Arrangement$Vertical;Landroidx/compose/ui/Alignment$Horizontal;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
|
||||
HSPLandroidx/compose/foundation/layout/RowKt;->Row(Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/layout/Arrangement$Horizontal;Landroidx/compose/ui/Alignment$Vertical;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
|
||||
|
||||
# Text
|
||||
HSPLandroidx/compose/material3/TextKt;->Text(Ljava/lang/String;Landroidx/compose/ui/Modifier;JJLandroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontSynthesis;Landroidx/compose/ui/text/style/TextAlign;Landroidx/compose/ui/text/style/TextDirection;JLandroidx/compose/ui/text/style/TextDecoration;Landroidx/compose/ui/text/style/TextGeometricTransform;Landroidx/compose/ui/text/style/TextIndent;Landroidx/compose/ui/text/PlatformTextStyle;Landroidx/compose/ui/text/style/LineHeightStyle;Landroidx/compose/ui/text/style/LineBreak;Landroidx/compose/ui/text/style/Hyphens;Landroidx/compose/ui/text/IntLocale;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/text/SpanStyle;Landroidx/compose/runtime/Composer;IIII)V
|
||||
|
||||
# Surface / MaterialTheme
|
||||
HSPLandroidx/compose/material3/SurfaceKt;->Surface(Landroidx/compose/ui/Modifier;Landroidx/compose/ui/graphics/Shape;JJFFLandroidx/compose/foundation/BorderStroke;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
|
||||
HSPLandroidx/compose/material3/MaterialTheme;-><clinit>()V
|
||||
|
||||
# Modifier
|
||||
HSPLandroidx/compose/ui/Modifier$Companion;-><clinit>()V
|
||||
HSPLandroidx/compose/ui/Modifier$Companion;->getInstance$annotations()V
|
||||
HSPLandroidx/compose/ui/Modifier;->then(Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
|
||||
|
||||
# ========== 动画相关 ==========
|
||||
|
||||
# updateTransition / animateFloat / animateColor
|
||||
HSPLandroidx/compose/animation/core/TransitionKt;->updateTransition(Ljava/lang/Object;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)Landroidx/compose/animation/core/Transition;
|
||||
HSPLandroidx/compose/animation/core/Transition$TransitionAnimationState;-><init>(Landroidx/compose/animation/core/Transition;Ljava/lang/Object;Landroidx/compose/animation/core/TwoWayConverter;Landroidx/compose/animation/core/AnimationVector;)V
|
||||
HSPLandroidx/compose/animation/core/Transition$TransitionAnimationState;->updateTargetValues$animation_core_release(Ljava/lang/Object;Landroidx/compose/animation/core/AnimationSpec;)V
|
||||
|
||||
# animateFloatAsState
|
||||
HSPLandroidx/compose/animation/core/AnimateAsStateKt;->animateFloatAsState(FLandroidx/compose/animation/core/AnimationSpec;FLkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State;
|
||||
|
||||
# ========== Pager 相关 ==========
|
||||
|
||||
# HorizontalPager
|
||||
HSPLandroidx/compose/foundation/pager/PagerKt;->HorizontalPager(Landroidx/compose/foundation/pager/PagerState;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/pager/PagerFlingBehavior;ZIILandroidx/compose/foundation/gestures/snapping/SnapPosition;Landroidx/compose/ui/input/nestedscroll/NestedScrollConnection;ZLkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V
|
||||
HSPLandroidx/compose/foundation/pager/PagerState;-><init>(IFLandroidx/compose/foundation/lazy/LazyListState;)V
|
||||
HSPLandroidx/compose/foundation/pager/PagerState;->getCurrentPage()I
|
||||
HSPLandroidx/compose/foundation/pager/PagerState;->getCurrentPageOffsetFraction()F
|
||||
|
||||
# ========== Sketch 图片加载 ==========
|
||||
|
||||
# AsyncImage
|
||||
HSPLio/github/panpf/sketch4/compose/AsyncImageKt;->AsyncImage(Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Alignment;Landroidx/compose/ui/layout/ContentScale;FLandroidx/compose/ui/graphics/ColorFilter;Landroidx/compose/ui/graphics/FilterQuality;ZLkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V
|
||||
|
||||
# ========== tyme4kt 农历计算 ==========
|
||||
|
||||
# SolarDay — 创建和查询
|
||||
HSPLcom/tyme/solar/SolarDay;->fromYmd(III)Lcom/tyme/solar/SolarDay;
|
||||
HSPLcom/tyme/solar/SolarDay;->getLunarDay()Lcom/tyme/lunar/LunarDay;
|
||||
HSPLcom/tyme/solar/SolarDay;->getTermDay()Lcom/tyme/solar/SolarTermDay;
|
||||
HSPLcom/tyme/solar/SolarDay;->getFestival()Lcom/tyme/solar/SolarFestival;
|
||||
HSPLcom/tyme/solar/SolarDay;->getLegalHoliday()Lcom/tyme/solar/LegalHoliday;
|
||||
|
||||
# LunarDay
|
||||
HSPLcom/tyme/lunar/LunarDay;->getFestival()Lcom/tyme/lunar/LunarFestival;
|
||||
HSPLcom/tyme/lunar/LunarDay;->getName()Ljava/lang/String;
|
||||
HSPLcom/tyme/lunar/LunarDay;->getLunarMonth()Lcom/tyme/lunar/LunarMonth;
|
||||
|
||||
# LunarMonth
|
||||
HSPLcom/tyme/lunar/LunarMonth;->getName()Ljava/lang/String;
|
||||
|
||||
# SolarTermDay
|
||||
HSPLcom/tyme/solar/SolarTermDay;->getDayIndex()I
|
||||
HSPLcom/tyme/solar/SolarTermDay;->getSolarTerm()Lcom/tyme/solar/SolarTerm;
|
||||
|
||||
# SolarTerm
|
||||
HSPLcom/tyme/solar/SolarTerm;->getName()Ljava/lang/String;
|
||||
@ -43,6 +43,19 @@ object LunarCache {
|
||||
trimIfNeeded()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整农历日期字符串,如"农历四月初三"。
|
||||
*
|
||||
* 复用缓存中的 lunarMonthName 和 annotationText,避免重复创建 SolarDay。
|
||||
*/
|
||||
@Synchronized
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API
|
||||
fun formatLunarDate(date: LocalDate): String {
|
||||
val info = getOrCompute(date)
|
||||
val dayText = info.annotationText.removeSuffix("月")
|
||||
return "农历${info.lunarMonthName}${dayText}"
|
||||
}
|
||||
|
||||
private fun trimIfNeeded() {
|
||||
if (cache.size > MAX_SIZE) {
|
||||
val toRemove = (cache.size * 0.2).toInt().coerceAtLeast(1)
|
||||
@ -61,34 +74,35 @@ object LunarCache {
|
||||
val solarDay = SolarDay.fromYmd(date.year, date.monthNumber, date.day)
|
||||
val holidayBadge = solarDay.getLegalHoliday()?.let { if (it.isWork()) "班" else "休" }
|
||||
val lunarDay = solarDay.getLunarDay()
|
||||
val lunarMonth = lunarDay.getLunarMonth()
|
||||
val lunarMonthName = lunarMonth.getName()
|
||||
|
||||
// 农历传统节日(仅当天)
|
||||
val lunarFestival = lunarDay.getFestival()
|
||||
if (lunarFestival != null) {
|
||||
return DayCellInfo(lunarFestival.getName(), true, holidayBadge)
|
||||
return DayCellInfo(lunarFestival.getName(), true, holidayBadge, lunarMonthName)
|
||||
}
|
||||
|
||||
// 节气(当天才显示)
|
||||
val termDay = solarDay.getTermDay()
|
||||
if (termDay.getDayIndex() == 0) {
|
||||
return DayCellInfo(termDay.getSolarTerm().getName(), true, holidayBadge)
|
||||
return DayCellInfo(termDay.getSolarTerm().getName(), true, holidayBadge, lunarMonthName)
|
||||
}
|
||||
|
||||
// 公历节日(仅当天)
|
||||
val solarFestival = solarDay.getFestival()
|
||||
if (solarFestival != null) {
|
||||
return DayCellInfo(solarFestival.getName(), true, holidayBadge)
|
||||
return DayCellInfo(solarFestival.getName(), true, holidayBadge, lunarMonthName)
|
||||
}
|
||||
|
||||
// 默认:农历日期
|
||||
val name = lunarDay.getName()
|
||||
val text = if (name == "初一") {
|
||||
val lunarMonth = lunarDay.getLunarMonth()
|
||||
"${lunarMonth.getName()}月"
|
||||
"${lunarMonthName}月"
|
||||
} else {
|
||||
name
|
||||
}
|
||||
return DayCellInfo(text, false, holidayBadge)
|
||||
return DayCellInfo(text, false, holidayBadge, lunarMonthName)
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,5 +116,6 @@ object LunarCache {
|
||||
data class DayCellInfo(
|
||||
val annotationText: String,
|
||||
val isAnnotationHighlight: Boolean,
|
||||
val holidayBadge: String?
|
||||
val holidayBadge: String?,
|
||||
val lunarMonthName: String? = null
|
||||
)
|
||||
|
||||
@ -17,6 +17,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@ -26,6 +27,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.datetime.LocalDate
|
||||
import plus.rua.project.CalendarViewModel
|
||||
import plus.rua.project.LunarCache
|
||||
import plus.rua.project.ShiftKind
|
||||
|
||||
/**
|
||||
@ -55,7 +57,7 @@ fun BottomCard(
|
||||
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口
|
||||
val solarDesc = "${selectedDate.monthNumber}月${selectedDate.day}日"
|
||||
val lunarDesc = formatLunarDate(selectedDate)
|
||||
val lunarDesc = remember(selectedDate) { LunarCache.formatLunarDate(selectedDate) }
|
||||
val shiftMessage = when (viewModel.shiftKindAt(selectedDate)) {
|
||||
ShiftKind.WORK -> "小小上班,轻松拿下!"
|
||||
ShiftKind.OFF -> "耶耶耶,美美休息!"
|
||||
|
||||
@ -552,16 +552,23 @@ private fun BottomCardArea(
|
||||
label = "bottomCardSlide"
|
||||
)
|
||||
|
||||
BottomCard(
|
||||
viewModel = viewModel,
|
||||
selectedDate = viewModel.selectedDate,
|
||||
today = today,
|
||||
dragRangePx = dragRangePx,
|
||||
modifier = modifier.graphicsLayer {
|
||||
translationY = slideProgress * 200.dp.toPx()
|
||||
alpha = 1f - slideProgress
|
||||
}
|
||||
)
|
||||
// P0-J: 延迟一帧显示 BottomCard,避免 AnimatedGif 和 lunar 计算阻塞首帧
|
||||
var frameCount by remember { mutableIntStateOf(0) }
|
||||
androidx.compose.runtime.SideEffect { frameCount++ }
|
||||
val shouldShow = frameCount >= 2
|
||||
|
||||
if (shouldShow) {
|
||||
BottomCard(
|
||||
viewModel = viewModel,
|
||||
selectedDate = viewModel.selectedDate,
|
||||
today = today,
|
||||
dragRangePx = dragRangePx,
|
||||
modifier = modifier.graphicsLayer {
|
||||
translationY = slideProgress * 200.dp.toPx()
|
||||
alpha = 1f - slideProgress
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@ -16,6 +16,7 @@ material3 = "1.10.0-alpha05"
|
||||
kotlinx-datetime = "0.8.0"
|
||||
tyme4kt = "1.4.5"
|
||||
sketch = "4.4.0"
|
||||
profileinstaller = "1.4.0"
|
||||
|
||||
[libraries]
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
@ -35,6 +36,7 @@ kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t
|
||||
tyme4kt = { module = "cn.6tail:tyme4kt", version.ref = "tyme4kt" }
|
||||
sketch-compose = { module = "io.github.panpf.sketch4:sketch-compose", version.ref = "sketch" }
|
||||
sketch-animated-gif = { module = "io.github.panpf.sketch4:sketch-animated-gif", version.ref = "sketch" }
|
||||
androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "profileinstaller" }
|
||||
|
||||
[plugins]
|
||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user