fix(ble): 화면 전환 후 좀비 BLE 세션 방지 — 콜백 순서 + 정리
증상: 앱 실행 후 alignment → 도넛차트 → 개발자모드 → alignment 이동 시 BLE 가 끊긴 것처럼 응답 실종 → 15초 후 watchdog forced reconnect. 심하면 재연결도 좀비 상태로 나와 3번째 재연결에서야 정상 동작. 원인 1: BleManager.onConnectionStateChange 에서 STATE_CONNECTED 직후 onConnectionStateChanged?.invoke(true) 호출 → 이 시점에는 아직 MTU/ service discovery/CCCD write 이 완료되지 않아 txCharacteristic 은 null. 상위 (PiezoMonitoringView) 가 startBatteryPolling 등 TX 를 시도하면 sendRaw 내부 null-check 에 걸려 silent drop → peripheral 이 응답을 보내지 않음 → watchdog timeout. 원인 2: PiezoMonitoringView / PlacementGuideView 의 DisposableEffect 에서 BleManager 콜백 (onConnectionStateChanged, onUnexpectedDisconnect, onReconnectionFailed, piezoCollector.onMultiChannelComplete 등) 을 정리하지 않음. 화면 전환 시 이전 화면의 stale 콜백이 남아 새 화면 진입 후에도 발동 → 상태 충돌. 수정: - BleManager: onConnectionStateChanged(true) 호출을 CCCD write 완료 (isServiceReady=true) 시점으로 이동. CCCD 없음 / RX 없음 edge case 도 동일하게 처리. - PiezoMonitoringView / PlacementGuideView: DisposableEffect 에서 등록한 BleManager 콜백 전부 null 로 정리. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -937,7 +937,12 @@ class BleManager private constructor(private val context: Context) {
|
||||
if (!gatt.requestMtu(247)) {
|
||||
gatt.discoverServices() // MTU 요청 실패 시 바로 진행
|
||||
}
|
||||
onConnectionStateChanged?.invoke(true)
|
||||
// 2026-07-07 fix: onConnectionStateChanged(true) 를 여기서 호출하면
|
||||
// TX/RX characteristic 이 아직 null (onServicesDiscovered 미실행) 이라
|
||||
// 상위 (PiezoMonitoringView) 가 startBatteryPolling 등 TX 를 시작해도
|
||||
// sendRaw 에서 silent drop → RX 실종 → watchdog timeout → forced reconnect
|
||||
// 좀비 세션의 원인. 콜백은 isServiceReady=true 시점 (onDescriptorWrite)
|
||||
// 으로 이동.
|
||||
}
|
||||
BluetoothProfile.STATE_DISCONNECTED -> {
|
||||
logd { "Disconnected from ${gatt.device.address}" }
|
||||
@@ -1011,6 +1016,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
handler.post {
|
||||
if (txCharacteristic != null && rxCharacteristic != null) {
|
||||
isServiceReady.value = true
|
||||
onConnectionStateChanged?.invoke(true)
|
||||
startBatteryPolling()
|
||||
startWatchdog()
|
||||
logd { "Service ready (no CCCD)" }
|
||||
@@ -1021,6 +1027,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
handler.post {
|
||||
if (txCharacteristic != null) {
|
||||
isServiceReady.value = true
|
||||
onConnectionStateChanged?.invoke(true)
|
||||
startBatteryPolling()
|
||||
startWatchdog()
|
||||
logd { "Service ready (no RX)" }
|
||||
@@ -1036,6 +1043,10 @@ class BleManager private constructor(private val context: Context) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
if (txCharacteristic != null && rxCharacteristic != null) {
|
||||
isServiceReady.value = true
|
||||
// 2026-07-07 fix: 여기서 상위에 알림 — TX/RX 준비 완료 시점. 이전에는
|
||||
// STATE_CONNECTED 즉시 호출해서 아직 characteristic null 인 상태로
|
||||
// 상위가 TX 시도 → silent drop → 좀비 세션.
|
||||
onConnectionStateChanged?.invoke(true)
|
||||
// Connection priority HIGH 요청: peripheral이 수락하면 interval ~15ms로 협상되어
|
||||
// Central Link Layer ACK 속도 ↑ → FW SoftDevice TX queue 포화 빈도 ↓
|
||||
val priorityOk = try {
|
||||
|
||||
Reference in New Issue
Block a user