From 72ccbdf1028a195c426f43a046ce817051aa33e8 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Fri, 8 May 2026 16:30:19 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Thread=E2=86=92coroutine,=20PlacementGui?= =?UTF-8?q?deView=20=EB=A9=94=EC=9D=B8=EC=8A=A4=EB=A0=88=EB=93=9C=20?= =?UTF-8?q?=EB=B3=B4=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #2: PiezoMonitoringView raw Thread → measureScope.launch + Dispatchers.Default withContext(Main)으로 state 변경 보장, composable 떠나면 자동 취소 - #22: PlacementGuideView onMultiChannelComplete 콜백 → handler.post 감싸기 BLE Binder 스레드에서 Compose state 직접 변경 → 메인 스레드 보장 - nested handler.post 제거, throttle 시 직접 state 리셋 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../views/monitoring/PiezoMonitoringView.kt | 22 +++++++++++-------- .../ui/views/monitoring/PlacementGuideView.kt | 3 +++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt index 2eaf41f..af83bde 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt @@ -106,6 +106,8 @@ fun PiezoMonitoringView(appState: AppState) { return sorted.subList(1, sorted.size - 1).average() } + val measureScope = rememberCoroutineScope() + fun measure() { if (isMeasuring) { Log.w("PiezoMonitor", "measure() called while already measuring — SKIPPED") @@ -129,11 +131,12 @@ fun PiezoMonitoringView(appState: AppState) { bleManager.piezoCollector.onMultiChannelComplete = { channels -> Log.d("PiezoMonitor", "RECEIVED ${channels.size} channels: ${channels.map { "CH${it.channel}(${it.buffer.size})" }}") // 분석을 백그라운드에서 수행 후 UI 업데이트는 메인 스레드로 - Thread { + measureScope.launch { + kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Default) { val analysisStart = System.currentTimeMillis() // channels를 로컬 복사 (Thread safety) val channelsCopy = channels.toList() - handler.post { + kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) { lastMultiChannelResult.clear() lastMultiChannelResult.addAll(channelsCopy) } @@ -303,14 +306,17 @@ fun PiezoMonitoringView(appState: AppState) { } // allWalls를 UI state에 저장 (ChannelPanel에서 사용) - handler.post { lastAllWalls = allWalls.toList() } + val wallsCopy = allWalls.toList() + kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) { + lastAllWalls = wallsCopy + } val finalRawVol = rawVolumeMl val finalTotalWalls = totalWallCount val analysisMs = System.currentTimeMillis() - analysisStart Log.d("PiezoMonitor", "Analysis took ${analysisMs}ms") // UI 업데이트는 메인 스레드에서 - handler.post { + kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) { if (finalRawVol != null && finalRawVol > 0) { if (isAutoMeasuring) { if (allChannelsReceived) { @@ -346,16 +352,14 @@ fun PiezoMonitoringView(appState: AppState) { measureRetryCount = 0 try { measureDoneSignal.value.complete(Unit) } catch (_: Exception) {} } - }.start() + } } } bleManager.piezoCollector.onLog = { msg -> Log.d("PiezoCollector", msg) } if (!bleManager.sendChannelsOnly()) { // Throttle됨 — isMeasuring 리셋 (deadlock 방지) - handler.post { - isMeasuring = false - try { measureDoneSignal.value.complete(Unit) } catch (_: Exception) {} - } + isMeasuring = false + try { measureDoneSignal.value.complete(Unit) } catch (_: Exception) {} return } diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt index 998034a..6e66f4b 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PlacementGuideView.kt @@ -525,6 +525,8 @@ fun PlacementGuideView(appState: AppState) { fun sendMeasureCommand() { bleManager.piezoCollector.onMultiChannelComplete = { channels -> + // 메인 스레드에서 처리 (BLE 콜백은 Binder 스레드) + handler.post { isScanning = false scanCount++ @@ -682,6 +684,7 @@ fun PlacementGuideView(appState: AppState) { direction = directionHint ) } + } // handler.post bleManager.piezoCollector.onLog = { msg -> Log.d("PlacementGuide", msg) } if (!bleManager.sendAllChannels()) { // Throttle됨 — isScanning 리셋 (deadlock 방지)