refactor(home): 收敛当前记录链路到 MainViewModel 并修正首页刷新时序#28
Conversation
- **日志优化**:
- 将 `DischargeRecordScanner` 中文件扫描成功的日志以及 `HistoryRepository` 中加载历史记录统计的日志级别由 `DEBUG` 调整为 `VERBOSE`,以减少调试模式下的日志干扰。
There was a problem hiding this comment.
Pull request overview
该 PR 将“首页当前记录(实时采样 + 当前分段加载/等待态)”链路收敛到 MainViewModel,并调整 server→app 的回调时序:当记录分段切换时,先通知新 RecordsFile,再下发已写入当前分段的实时样本,以修正首页刷新/切段时的展示一致性。
Changes:
- server 写入链路改为返回
WriteResult(accepted, changedRecordsFile),并在回调线程按“文件切换→样本”顺序通知 app - app 侧移除
LiveRecordViewModel,新增CurrentRecordUiState,首页当前记录卡片直接消费MainViewModel.currentRecordUiState HistoryRepository新增按指定RecordsFile加载当前记录的结果类型(成功/缺失/样本不足/失败),支持“等待新分段有效样本”状态
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| server/src/main/java/yangfentuozi/batteryrecorder/server/writer/PowerRecordWriter.kt | 写入接口返回 WriteResult,在分段创建/状态切换后产出 changedRecordsFile |
| server/src/main/java/yangfentuozi/batteryrecorder/server/recorder/Monitor.kt | 采样回调中按 changedRecordsFile→onRecord 顺序通知监听者,并过滤未接受样本 |
| server/src/main/aidl/yangfentuozi/batteryrecorder/server/recorder/IRecordListener.aidl | onChangedCurrRecordsFile 改为携带 RecordsFile 参数 |
| app/src/main/java/yangfentuozi/batteryrecorder/ui/viewmodel/MainViewModel.kt | 聚合首页当前记录状态与刷新模式,新增等待态/实时点缓冲与分段跟踪逻辑 |
| app/src/main/java/yangfentuozi/batteryrecorder/ui/viewmodel/LiveRecordViewModel.kt | 删除(实时点与状态并入 MainViewModel) |
| app/src/main/java/yangfentuozi/batteryrecorder/ui/screens/home/HomeScreen.kt | listener 回调对接到 MainViewModel,并调整统计刷新触发时序 |
| app/src/main/java/yangfentuozi/batteryrecorder/ui/model/CurrentRecordUiState.kt | 新增:首页当前记录卡片展示状态与实时采样数据结构 |
| app/src/main/java/yangfentuozi/batteryrecorder/ui/components/home/CurrentRecordCard.kt | 改为消费 CurrentRecordUiState,支持“分段切换等待态”展示 |
| app/src/main/java/yangfentuozi/batteryrecorder/data/history/HistoryRepository.kt | 新增 loadCurrentRecord(recordsFile) 与结果类型,区分样本不足/文件缺失/失败 |
| app/src/main/java/yangfentuozi/batteryrecorder/ui/dialog/settings/CalibrationDialog.kt | 适配 AIDL 方法签名变化(忽略 recordsFile 参数) |
| CLAUDE.md | 更新文档:首页当前记录链路收敛到 MainViewModel |
| AGENTS.md | 更新文档:首页当前记录链路收敛到 MainViewModel |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pendingCurrentRecordsFile = nextPendingRecordsFile | ||
| activeLiveRecordsFileName = | ||
| nextPendingRecordsFile?.name ?: resolvedCurrentRecord?.name ?: activeLiveRecordsFileName | ||
|
|
||
| val nextDisplayStatus = when { |
There was a problem hiding this comment.
When统计刷新过程中 targetRecordsFile/activeLiveRecordsFileName 发生变化(例如从旧分段切到 serviceCurrentRecordsFile 或 resolvedCurrentRecord),这里会继续复用 liveBuffer 并直接写入 currentRecordUiState.livePoints,可能导致新分段/新语义下仍展示上一分段的实时曲线点。建议在检测到 activeLiveRecordsFileName 变更时清空 liveBuffer(或按文件名分桶缓存),并同步更新 currentRecordUiState.livePoints 为空,避免跨分段污染。
| viewModel.refreshStatisticsTrackingCurrentRecord( | ||
| context = context, | ||
| request = statisticsRequest | ||
| ) |
There was a problem hiding this comment.
这个 LaunchedEffect 在 delay(1500) 前检查了 lifecycle 状态,但 delay 结束后没有再次确认仍处于 STARTED/RESUMED,就会在用户已离开首页或进入后台时触发 refreshStatisticsTrackingCurrentRecord,造成不必要的 Sync/IO 与状态更新。建议在 delay 后再次检查 lifecycle(或用 repeatOnLifecycle/withFrameNanos 等更生命周期安全的方式)再执行刷新。
| viewModel.refreshStatisticsTrackingCurrentRecord( | |
| context = context, | |
| request = statisticsRequest | |
| ) | |
| if (lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { | |
| viewModel.refreshStatisticsTrackingCurrentRecord( | |
| context = context, | |
| request = statisticsRequest | |
| ) | |
| } |
| private fun Throwable.isInsufficientSamplesError(): Boolean { | ||
| return this is IllegalStateException && | ||
| message?.startsWith(NOT_ENOUGH_VALID_SAMPLES_PREFIX) == true | ||
| } |
There was a problem hiding this comment.
isInsufficientSamplesError 通过匹配 IllegalStateException 的 message 前缀来判断“样本不足”,这对 RecordsStats 的错误文案有强耦合,后续一旦调整异常信息就会导致等待态判断失效。建议改为抛出/传递一个明确的异常类型(或在 shared 层提供可复用的常量/错误码)来做分支判断。
- **采样流程重构**:
- 抽取 `waitUntilSamplingAllowedLocked` 方法,统一管理采样前的状态判定逻辑。
- 将采样主循环简化为“等待授权 -> 执行采样 -> 间隔等待”的线性流程,提高代码可读性。
- **性能优化**:
- 在轮询模式下,仅在进入判定循环时刷新亮屏状态,避免每次写入文件前执行额外的系统调用(`iPowerManager.isInteractive`),降低开销。
- **状态管理**:
- 优化了暂停与恢复采样的日志记录与状态切换逻辑。
- 明确区分了基于回调的亮屏状态监听与基于轮询的刷新机制,确保采样行为符合 `screenOffRecord` 配置。
- **数据模型**:
- 在 `CurrentRecordUiState` 中新增 `recordsFileName` 字段,用于标识当前 UI 状态所属的记录文件。
- 引入私有数据结构 `LiveSegmentBuffer` 统一管理实时采样点及其对应的文件名,替代原有的分散变量。
- **状态同步与切换**:
- 新增 `switchActiveLiveSegment` 方法,在记录文件切换时强制清空采样缓存,确保采样曲线与分段语义严格对应,避免旧数据的残影。
- 重构 `onLiveSampleReceived` 逻辑,增加对 `pendingCurrentRecordsFile` 的校验,确保在实时采样到来时 UI 状态能正确关联到目标文件。
- 优化 `onLiveStatusChanged` 中的状态推导逻辑,根据最新的 `resolvedCurrentRecord` 或 Service 状态同步更新活动分段,并使用快照更新 UI。
- **稳定性与清理**:
- 统一 `clearDisplayedHomeState` 的清理逻辑,确保重置时同步清除 `liveSegmentBuffer` 中的状态。
- **逻辑重构**:移除 `buildChangedRecordsFile` 私有方法,将其逻辑内联至 `write` 方法的主流程中,直接处理 `segmentFile` 的状态转换。 - **日志增强**:优化了文件切换时的日志输出,明确区分“新分段已就绪”与“状态切换后续写当前分段”两种触发原因,并确保日志中的文件信息与返回的 `WriteResult` 保持一致。
- **逻辑重构**:
- 移除 `waitUntilSamplingAllowedLocked` 方法,将采样等待逻辑直接整合进 `MonitorThread` 的主循环中,简化执行流程。
- 调整采样与等待的顺序,确保在亮屏或开启熄屏记录时先执行一次采样再进入间隔等待。
- **亮屏状态监测**:
- 优化了 `alwaysPollingScreenStatusEnabled` 模式下的状态更新逻辑。在暂停采样期间,通过循环轮询 `iPowerManager.isInteractive` 状态,确保能及时响应屏幕状态变化并恢复采样。
- 统一了状态变化的日志输出,便于追踪亮屏状态切换。
- **状态管理**:
- 细化了 `paused` 状态的切换时机,确保 UI 或日志能准确反映当前的采样挂起状态。
- **数据层**:
- **PowerRecordWriter**:将 `WriteResult` 从 `data class` 重构为 `sealed interface`,明确划分为 `Accepted`(已写入)、`Rejected`(已丢弃)和 `Changed`(文件已切换)三种状态。
- 优化了 `BaseDelayedRecordWriter` 的写入逻辑。在文件切换时强制返回 `Changed` 状态,并收紧了状态转换的语义,消除原先布尔组合产生的歧义。
- 增加对 `segmentFile` 异常为空时的错误日志埋点,以便观察采样线程的状态同步问题。
- **核心逻辑**:
- **Monitor**:适配新的 `WriteResult` 密封类。在采样循环中根据不同状态精确触发回调,确保只有在 `Accepted` 或 `Changed` 时才分发 `onRecord`,并在 `Changed` 时同步更新记录文件。
No description provided.