说明文档及下载链接

问题 1:追加导入坐标库时锚点被当作普通点

现象

使用"添加"(追加)模式导入坐标库时,文件中 IsAnchor=1 的锚点被当成普通坐标点存入数据库,不会在里程校准中发挥作用。

根因分析

1.1 追加后缺少自动校准

MainScreens.ktMappingScreen 中,导入流程为:

用户点击 "添加" 按钮 -> viewModel.doImport(context, refs, "添加") -> viewModel.clearPendingImport()

doImport 方法:

fun doImport(c: Context, refs: List<MileageReference>, mode: String) { 
    viewModelScope.launch { 
        withContext(Dispatchers.IO) { 
            if(mode=="覆盖") 
                database?.detectionDao()?.overwriteMileageLibrary(refs) 
            else 
                database?.detectionDao()?.insertMileageRefs(refs)
        }; 
        Toast.makeText(c, "Imported ${refs.size}", Toast.LENGTH_SHORT).show() 
    } 
}

追加模式下直接调用 insertMileageRefs(refs)(Room 的 @Insert(onConflict = OnConflictStrategy.REPLACE))。这个方法按 primaryKey 判断冲突,并不区分普通点和锚点。锚点存进去就只是 isAnchor = true 的普通记录,不会触发后续的样条校准

1.2 关键问题:未调用 calibrateLine

即便锚点成功存入,问题是在追加完成后从未调用过 calibrateLine()。这导致锚点虽然存在于数据库,但没有触发 Hermite 样条校准,锚点实际上不生效。

对比「插入锚点」功能(doInsertQuickPoint),它在插入之后会主动调用 calibrateLine(effectiveLine, effectiveDir),因此锚点立即生效。

修改

1:doImport 追加完成后,自动触发 calibrateLine。对导入的所有 (lineName, lineDirection) 组合执行校准。

fun doImport(c: Context, refs: List<MileageReference>, mode: String) { 
    viewModelScope.launch { 
        withContext(Dispatchers.IO) { 
            if(mode=="覆盖") 
                database?.detectionDao()?.overwriteMileageLibrary(refs) 
            else 
                database?.detectionDao()?.insertMileageRefs(refs)
            
            // 【修复】追加后自动校准所有受影响线路
            val linePairs = refs.map { it.lineName to it.lineDirection }.distinct()
            linePairs.forEach { (name, dir) -> calibrateLine(name, dir) }
        }
        withContext(Dispatchers.Main) {
            Toast.makeText(c, "Imported ${refs.size}", Toast.LENGTH_SHORT).show()
        }
    } 
}

问题 2:GPS 偶发卡死

现象

检测过程中 GPS 定位偶尔停止更新(UI 经纬度、速度卡住不变),但 app 未崩溃。

根因分析

2.1 reRequestLocationUpdates 存在竞态条件

有两处会调用 reRequestLocationUpdates

  1. adjustLocationStrategy()(每 30 秒调用)
  2. stopServiceIfIdle()(停止检测时调用)
private fun reRequestLocationUpdates(interval: Long) {
    try {
        fusedLocationClient.removeLocationUpdates(locationCallback)
        val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, interval)
            .setMinUpdateIntervalMillis(interval / 2).build()
        fusedLocationClient.requestLocationUpdates(request, locationCallback, Looper.getMainLooper())
    } catch (_: SecurityException) {}
}

如果在 removeLocationUpdatesrequestLocationUpdates 之间发生异常,或 Google Play Services 内部超时,定位服务会永久停止回调。

2.2 locationCallback 的 return 分支阻塞 lastGpsFixTime

if (loc.accuracy <= 50f) {
    lastGpsFixTime = currentTime
} else {
    lastValidSpeed = smoothedSpeed
    return  // 精度 > 50m 直接 return
}

当连续收到低精度定位时,lastGpsFixTime 不更新。若此时位置服务回调消失(进入隧道),会以假死状态维持数秒。

2.3 欠缺超时恢复机制

目前没有对「GPS 无任何回调超过 N 秒」的检测与自动恢复。当 reRequestLocationUpdates 失败时,除非重启 app,否则定位永久失效。

修改

1: 添加 GPS 心跳超时保护:

private var lastLocationCallbackTime: Long = 0L

// locationCallback 开头记录
override fun onLocationResult(result: LocationResult) {
    lastLocationCallbackTime = currentTime
    // ...
}

// startDeadReckoningTimer 循环中增加
if ((currentTime - lastLocationCallbackTime) > 10000) {
    reRequestLocationUpdates(lastRequestInterval)
    lastLocationCallbackTime = currentTime
}

2: 分离移除和注册,加入延迟:

private fun reRequestLocationUpdates(interval: Long) {
    serviceScope.launch {
        try {
            fusedLocationClient.removeLocationUpdates(locationCallback)
            delay(100)
            val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, interval)
                .setMinUpdateIntervalMillis(interval / 2).build()
            fusedLocationClient.requestLocationUpdates(request, locationCallback, Looper.getMainLooper())
        } catch (_: SecurityException) {}
    }
}

问题 3:隧道 GPS 丢失时里程推算卡滞

现象

进入隧道 GPS 丢失后,里程停顿 7-8 秒才推算,有时完全不推。

根因分析

3.1 GPS 丢失检测延迟

if ((currentTime - lastGpsFixTime) > 1500) {
    // 进入推算模式

进入推算模式前已有 1.5 秒的盲区。且 lastGpsFixTime 仅在精度 <= 50m 时更新,精度 > 50m 时 return 但不更新。

时间线:

  1. 列车进入隧道 → GPS 精度 > 50m → lastGpsFixTime 不再更新
  2. locationCallback 仍被调用但每次都 return
  3. 1.5 秒后触发推算
  4. 首次推算在下一轮 1 秒循环执行
  5. 合计盲区约 2~2.5 秒

3.2 推算速度是开环指数衰减

val decayFactor = exp(-secondsSinceLoss / speedDecayTau)  // tau = 30s
val displaySpeed = max(
    estimatedIntegralSpeed.toDouble(),
    gpsSpeedAtLoss.toDouble() * decayFactor
)

estimatedIntegralSpeed 初始化为 lastValidSpeed从未更新(没有加速度积分)。速度完全是开环指数衰减——不依赖任何实时传感器。

陀螺仪数据 currentGyroZ 只影响航向(bearing),不影响速度推算。

列车在隧道中加速/减速完全无法反映到推算速度上。

3.3 GPS 恢复时回切延迟

isGpsLost 复位在 Layer 2(受 isDbSearching 原子布尔控制的协程)中执行:

if (!isDbSearching.get()) {
    metadataJob = serviceScope.launch {
        isDbSearching.set(true)
        // 大量 I/O 操作...
        if (isGpsLost && ...) { ... isGpsLost = false }
    }
}

如果前一次数据库查询未完成,locationCallback 可能连续数次被跳过,延迟数秒。

修改

1: 降低丢失阈值从 1500ms 到 800ms:

if ((currentTime - lastGpsFixTime) > 800) {

2: 精度 > 50m 但有速度信号时,更新 lastGpsFixTime

if (loc.accuracy <= 50f) {
    lastGpsFixTime = currentTime
} else if (loc.hasSpeed()) {
    lastGpsFixTime = currentTime  // 有速度信号也更新
    lastValidSpeed = smoothedSpeed
    // 移除 return
}

3: 在推算速度中加入加速度计积分。当前已有陀螺仪数据 currentGyroZ,可从加速度计动态分量提取沿前进方向加速度进行积分:

// 在 onSensorChanged 中:
// 添加类变量 forwardAccel
val bearingRad = Math.toRadians(lastBearing.toDouble())
forwardAccel = filteredX * sin(bearingRad) + filteredZ * cos(bearingRad)

// 在推算循环中:
val accelMps = forwardAccel * 9.81f  // g 转 m/s^2
estimatedIntegralSpeed = (estimatedIntegralSpeed + accelMps * dtSec)
    .coerceIn(0f, MAX_SPEED_MPS)

4:isGpsLost = false 的复位提前到 Layer 1:

// Layer 1 末尾:
if (isGpsLost) {
    _serviceState.update { it.copy(isGpsLost = false) }
    isGpsLost = false
}
// Layer 2 中不再设置 isGpsLost

问题 4:传感器参数 Alpha/Gain/Scale 未正确传递

现象

设置页调整 filterAlpha(灵敏度)、baseGain(增益)、scaleX/scaleZ(幅值补偿)后,传感器表现没有变化。

根因分析

4.1 UPDATE_CONFIG 分支缺失

InspectorViewModel.updateSettings() 向 Service 发送 UPDATE_CONFIG Intent:

fun updateSettings(context: Context, newState: InspectorState) {
    // ... 写入 SharedPreferences ...
    _state.update { newState }
    if (boundServiceRef?.get() != null) {
        val intent = Intent(context, InspectorService::class.java).apply {
            action = "UPDATE_CONFIG"
            putExtra("FILTER_ALPHA", newState.filterAlpha)
            putExtra("BASE_GAIN", newState.baseGain)
            putExtra("SCALEX", newState.scaleX)
            putExtra("SCALEZ", newState.scaleZ)
        }
        context.startService(intent)
    }
}

InspectorService.onStartCommand() 中没有 "UPDATE_CONFIG" 的分支!

when (intent?.action) {
    "START_DETECTION" -> { ... }
    "STOP_DETECTION" -> { ... }
    "START_MAPPING" -> { ... }
    "STOP_MAPPING" -> { ... }
    "START_LIGHTWEIGHT" -> { ... }
    // 缺少 "UPDATE_CONFIG"!
}

参数从未被写入 Service 的 _serviceState 滑动滑块改变参数后,对传感器处理完全没有效果。

4.2 参数仅在 START_DETECTION 时读取一次

"START_DETECTION" -> {
    _serviceState.update { it.copy(
        filterAlpha = intent.getFloatExtra("FILTER_ALPHA", it.filterAlpha),
        baseGain = intent.getFloatExtra("BASE_GAIN", it.baseGain),
        scaleX = intent.getFloatExtra("SCALEX", it.scaleX),
        scaleZ = intent.getFloatExtra("SCALEZ", it.scaleZ),
    ) }

如果用户在检测过程中调整参数,参数写入 ViewModel 的 _state,但永远不会同步到 Service 的 _serviceStateonSensorChanged 读取的是 Service 端 _serviceState,完全不受 UI 调整影响。

4.3 baseGain 在传感器计算中完全被忽略

// onSensorChanged 中的计算逻辑:
val ax = dynamicX * s.scaleX - s.offsetX
val az = dynamicZ * s.scaleZ - s.offsetZ

val alpha = s.filterAlpha
filteredX = ax * alpha + filteredX * (1 - alpha)
filteredZ = az * alpha + filteredZ * (1 - alpha)

s.baseGain 完全没有出现在任何计算中! 虽然 InspectorState 中定义了 baseGain = 2.52f,但在关键计算管道中被忽略了。设置页面上的全局灵敏度增益滑块不产生任何作用。

4.4 syncServiceState 不同步传感器参数

fun syncServiceState(serviceState: InspectorState) {
    _state.update { s -> s.copy(
        // ... 缺少 filterAlpha, baseGain, scaleX, scaleZ, offsetX, offsetZ
    ) }
}

ViewModel 和 Service 双向不同步,导致两端各自维护一套参数,相互覆盖。

修改

1(最关键):InspectorService.onStartCommand() 中添加 "UPDATE_CONFIG" 分支:

"UPDATE_CONFIG" -> {
    _serviceState.update { it.copy(
        filterAlpha = intent?.getFloatExtra("FILTER_ALPHA", it.filterAlpha) ?: it.filterAlpha,
        baseGain = intent?.getFloatExtra("BASE_GAIN", it.baseGain) ?: it.baseGain,
        scaleX = intent?.getFloatExtra("SCALEX", it.scaleX) ?: it.scaleX,
        scaleZ = intent?.getFloatExtra("SCALEZ", it.scaleZ) ?: it.scaleZ,
        offsetX = intent?.getFloatExtra("OFFSETX", it.offsetX) ?: it.offsetX,
        offsetZ = intent?.getFloatExtra("OFFSETZ", it.offsetZ) ?: it.offsetZ,
    ) }
}

2:onSensorChanged 中加入 baseGain

val totalGain = s.baseGain
val ax = (dynamicX * s.scaleX - s.offsetX) * totalGain
val az = (dynamicZ * s.scaleZ - s.offsetZ) * totalGain

3:syncServiceState 添加传感器参数同步:

s.copy(
    // ... 现有字段 ...
    filterAlpha = serviceState.filterAlpha,
    baseGain = serviceState.baseGain,
    scaleX = serviceState.scaleX,
    scaleZ = serviceState.scaleZ,
    offsetX = serviceState.offsetX,
    offsetZ = serviceState.offsetZ,
)

总结

问题 根因 严重性 修改量
1. 追加导入锚点无效 导入后未调用 calibrateLine ~2 行
2. GPS 偶发卡死 reRequestLocationUpdates 无恢复机制 ~15 行
3. 隧道推算延迟/失效 丢失阈值高、速度无加速度计积分 ~30 行
4. 传感器参数无效 UPDATE_CONFIG 分支缺失、baseGain 未使用 极高 ~10 行

问题 4 最为关键:用户感觉"手机不如以前灵敏"的根本原因是参数虽然在 UI 上变化了,但传感器计算管道从未应用这些变化。设置页面上的所有 Alpha/Gain/Scale 滑块都是"摆设"。