说明文档及下载链接

一、数据库与数据持久化问题

1.1 破坏性迁移导致数据丢失

文件: InspectorDatabase.kt:273
严重度: 严重

.fallbackToDestructiveMigration()

数据库当前版本: v13

问题:

  • fallbackToDestructiveMigration() 会在数据库版本升级时删除所有表并重建
  • 从 v1 到 v13 没有任何 Migration 路径
  • 用户升级应用会丢失所有历史检测记录和坐标库数据
  • 对于检测现场来说,数据丢失是灾难性的

修改:

  • 上架前必须为 version = 13 之前的每个版本编写 Migration 代码
  • 或在首个上架版本设置 version = 1,清除开发阶段的版本历史
  • 设置 exportSchema = true 并配置 room.schemaLocation 以追踪架构变更

1.2 DAO 查询冗余和性能隐患

文件: InspectorDatabase.kt:109-165
严重度: 中

存在大量功能重叠的 DAO 查询:

  • findNearestPoints, findNearestTwoPoints, findNearestPointsBounded (x2), findNearestPointsAll, findNearestTwoPointsAll, findNearestPointsAllBounded, findNearestTwoPointsBounded

问题:

  • 8 个类似的查询方法,维护成本高
  • 无空间索引优化(虽然加了 INDEX 但只在 (latitude, longitude) 上,欧氏距离排序无法利用索引)
  • ORDER BY ((lat-:lat)² + (lng-:lng)²) 在大表上性能极差

修改:

  • 保留 2-3 个核心查询方法即可
  • 使用 R-tree 空间索引(Room 支持 @Fts4

1.3 Transactions 未充分使用

文件多处: InspectorDatabase.kt, MainActivity.kt:1854

问题:

  • records.chunked(500).forEach { batch -> database?.detectionDao()?.insertAll(batch) } 逐批插入但未包在事务中
  • 批量插入时如果没有事务包装,每个 insertAll 可能开启独立的事务

修改:

  • 大数据量写入时使用 @Transaction 包装

二、架构与代码质量

2.1 MainActivity.kt 4002 行 — 严重违反单一职责

严重度: 严重

这个文件包含了:

  • MainActivity(Activity 生命周期)
  • InspectorViewModel(View Model + 业务逻辑)
  • InspectorState(数据类)
  • Screen(导航)
  • Trans(多语言词典)
  • 全部 10+ 个 Composable 界面函数
  • 自定义图表、仪表盘等 UI 组件

修改:

  • Trans 对象拆分为独立文件 Translations.kt
  • InspectorViewModel 拆分为独立文件
  • 将 Composable 界面按功能拆分到多个文件
  • 每个文件控制在 400 行以内

2.2 传感器数据处理逻辑双重实现

文件:

  • InspectorService.kt:542-671(Service 中的 onSensorChanged
  • MainActivity.kt:1159-1267(ViewModel 中的 onSensorChanged

严重度: 高

问题:

  • Service 和 ViewModel 各自有几乎完全相同的传感器数据处理逻辑(重力对齐、滤波、峰值检测、超限计算)
  • 两处逻辑使用不同的参数(gravityAlpha 不同:Service 用 0.015,ViewModel 用 0.0005)
  • 两处逻辑可能同时注册传感器监听,导致冲突

建议:

  • 传感器数据仅在 Service 中处理
  • ViewModel 通过 StateFlow 观察 Service 的状态
  • 将传感器算法抽取为独立工具类

2.3 双重 GPS 订阅

文件:

  • InspectorService.kt:283(Service 的 fusedLocationClient.requestLocationUpdates
  • MainActivity.kt:1149(ViewModel 的 fusedLocationClient?.requestLocationUpdates

严重度: 高

问题:

  • Service 和 ViewModel 同时订阅位置更新
  • 虽然 observeService 中调用了 stopLocationUpdates(),但流程并非完全可靠
  • 两个 location callback 都会查询数据库,导致不必要的 I/O

建议:

  • GPS 位置更新仅在 Service 中处理
  • ViewModel 不应独立订阅位置

2.4 未使用枚举/密封类表示状态

多处:

问题:

  • 超限等级使用 Int(0,1,2,3)
  • 超限类型使用 String("NORMAL", "H1", "V2", "H3 V3")
  • 行别使用 String("上行", "下行", "单线")

建议:

  • 使用 sealed classenum class 提高类型安全性

三、计算逻辑缺陷

3.1 水平加速度方向判定不精确

文件: InspectorService.kt:589MainActivity.kt:1210

val hAccSigned = if (lx < 0) -hAccMagnitude else hAccMagnitude

问题:

  • 仅用 x 轴的符号来决定水平加速度方向
  • 当手机在车上非标准安装时,x 轴与轨道方向不对齐
  • 导致水平超限的方向判断不准确

建议:

  • 应基于重力方向投影完整计算水平矢量方向
  • 或使用航向角传感器进行轴对齐

3.2 死区阈值过于精细

文件: InspectorService.kt:604-605

val finalV = if (abs(avgV) < 0.008f) 0f else avgV
val finalH = if (abs(avgH) < 0.012f) 0f else avgH

问题:

  • 0.008G 和 0.012G 的死区阈值非常小(接近传感器噪声水平)
  • 可能导致频繁的 0 值与微小值切换,造成波形毛刺

建议:

  • 提高死区阈值至 0.02-0.03G 范围
  • 或使用自适应死区(基于当前噪声水平)

3.3 KalmanFilter 参数固定

文件: InspectorService.kt:98-107

private class KalmanFilter(private val processNoise: Float, private val measurementNoise: Float)

实例化: speedKalman = KalmanFilter(0.1f, 1.0f)

问题:

  • measurementNoise 固定为 1.0f,不随 GPS 精度变化
  • 当 GPS 精度 3m 时与精度 50m 时使用相同权重,不合理

建议:

  • 根据 loc.accuracy 动态调整测量噪声协方差

3.4 里程推算的边界条件

文件: InspectorService.kt:350-387

val dtSec = (dtMillis / 1000.0).coerceAtMost(5.0)
// ...
val displacementMeters = currentSpeed * dtSec

问题:

  • dtSec 被限制在 5 秒内,但 MAX_DEAD_RECKONING_MS 可达 30 分钟
  • 当 GPS 丢失超过 5 秒时,dtSec 被钳制到 5 秒,但 lastValidTime 也在每次迭代后被更新
  • 这意味着每秒增加 currentSpeed * 5 米的位移 → 里程估算被放大 5 倍

分析:

  • 每次循环 lastValidTime = currentTime(行 385)
  • 下一轮 dtMillis = 1000(因为循环每秒执行一次)
  • dtSec = min(1000/1000, 5) = 1
  • 所以实际上 dtSec 每次都是 1 秒
  • 但如果 lastValidTime 没有被更新(即链路异常),会出现 dtSec 被钳制到 5 的情况

建议: 这个逻辑整体上看是正确的,但需要增加防御性检查和单元测试


四、性能问题

4.1 波形图全量加载

文件: MainActivity.kt:2921

records = viewModel.getSessionRecords(sessionId)

问题:

  • 一次加载全部记录(可能上万条)
  • 在 UI 线程处理列表操作

4.2 数据库查询频繁

每个 GPS 定位都会触发:

  1. findNearestPoints(5条SQL)
  2. findNearestPointsAll(5条SQL)
  3. getFeaturesNearMileage(1条SQL)

当 GPS 以 500ms-1000ms 间隔更新时,数据库 I/O 压力大

4.3 坐标库全量加载

  • getAllMileageRefs() 用于导出
  • 锚点遍历使用所有锚点

4.4 导出时的内存使用

  • 导出 CSV 时构建 StringBuilder 可能会占用大量内存

五、测试覆盖

5.1 仅有默认测试文件

  • ExampleUnitTest.kt - 空测试
  • ExampleInstrumentedTest.kt - 空测试
  • 零实际的单元测试和集成测试

5.2 关键逻辑无测试覆盖

  • 里程计算 MileageUtils.calculateMileageValue
  • 位移计算 calculateDisplacedMileage
  • 传感器滤波算法
  • GPS 丢失后的推算逻辑
  • 授权码的签名验证

修改:

  • MileageUtils 编写单元测试
  • 为授权验证逻辑编写测试
  • 为数据库 DAO 编写 Android 测试