fix: Thread→coroutine, PlacementGuideView 메인스레드 보장
- #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) <noreply@anthropic.com>
This commit is contained in:
+13
-9
@@ -106,6 +106,8 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
return sorted.subList(1, sorted.size - 1).average()
|
return sorted.subList(1, sorted.size - 1).average()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val measureScope = rememberCoroutineScope()
|
||||||
|
|
||||||
fun measure() {
|
fun measure() {
|
||||||
if (isMeasuring) {
|
if (isMeasuring) {
|
||||||
Log.w("PiezoMonitor", "measure() called while already measuring — SKIPPED")
|
Log.w("PiezoMonitor", "measure() called while already measuring — SKIPPED")
|
||||||
@@ -129,11 +131,12 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
bleManager.piezoCollector.onMultiChannelComplete = { channels ->
|
bleManager.piezoCollector.onMultiChannelComplete = { channels ->
|
||||||
Log.d("PiezoMonitor", "RECEIVED ${channels.size} channels: ${channels.map { "CH${it.channel}(${it.buffer.size})" }}")
|
Log.d("PiezoMonitor", "RECEIVED ${channels.size} channels: ${channels.map { "CH${it.channel}(${it.buffer.size})" }}")
|
||||||
// 분석을 백그라운드에서 수행 후 UI 업데이트는 메인 스레드로
|
// 분석을 백그라운드에서 수행 후 UI 업데이트는 메인 스레드로
|
||||||
Thread {
|
measureScope.launch {
|
||||||
|
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Default) {
|
||||||
val analysisStart = System.currentTimeMillis()
|
val analysisStart = System.currentTimeMillis()
|
||||||
// channels를 로컬 복사 (Thread safety)
|
// channels를 로컬 복사 (Thread safety)
|
||||||
val channelsCopy = channels.toList()
|
val channelsCopy = channels.toList()
|
||||||
handler.post {
|
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) {
|
||||||
lastMultiChannelResult.clear()
|
lastMultiChannelResult.clear()
|
||||||
lastMultiChannelResult.addAll(channelsCopy)
|
lastMultiChannelResult.addAll(channelsCopy)
|
||||||
}
|
}
|
||||||
@@ -303,14 +306,17 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// allWalls를 UI state에 저장 (ChannelPanel에서 사용)
|
// 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 finalRawVol = rawVolumeMl
|
||||||
val finalTotalWalls = totalWallCount
|
val finalTotalWalls = totalWallCount
|
||||||
val analysisMs = System.currentTimeMillis() - analysisStart
|
val analysisMs = System.currentTimeMillis() - analysisStart
|
||||||
Log.d("PiezoMonitor", "Analysis took ${analysisMs}ms")
|
Log.d("PiezoMonitor", "Analysis took ${analysisMs}ms")
|
||||||
|
|
||||||
// UI 업데이트는 메인 스레드에서
|
// UI 업데이트는 메인 스레드에서
|
||||||
handler.post {
|
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) {
|
||||||
if (finalRawVol != null && finalRawVol > 0) {
|
if (finalRawVol != null && finalRawVol > 0) {
|
||||||
if (isAutoMeasuring) {
|
if (isAutoMeasuring) {
|
||||||
if (allChannelsReceived) {
|
if (allChannelsReceived) {
|
||||||
@@ -346,16 +352,14 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
measureRetryCount = 0
|
measureRetryCount = 0
|
||||||
try { measureDoneSignal.value.complete(Unit) } catch (_: Exception) {}
|
try { measureDoneSignal.value.complete(Unit) } catch (_: Exception) {}
|
||||||
}
|
}
|
||||||
}.start()
|
} }
|
||||||
}
|
}
|
||||||
bleManager.piezoCollector.onLog = { msg -> Log.d("PiezoCollector", msg) }
|
bleManager.piezoCollector.onLog = { msg -> Log.d("PiezoCollector", msg) }
|
||||||
|
|
||||||
if (!bleManager.sendChannelsOnly()) {
|
if (!bleManager.sendChannelsOnly()) {
|
||||||
// Throttle됨 — isMeasuring 리셋 (deadlock 방지)
|
// Throttle됨 — isMeasuring 리셋 (deadlock 방지)
|
||||||
handler.post {
|
isMeasuring = false
|
||||||
isMeasuring = false
|
try { measureDoneSignal.value.complete(Unit) } catch (_: Exception) {}
|
||||||
try { measureDoneSignal.value.complete(Unit) } catch (_: Exception) {}
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
@@ -525,6 +525,8 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
|
|
||||||
fun sendMeasureCommand() {
|
fun sendMeasureCommand() {
|
||||||
bleManager.piezoCollector.onMultiChannelComplete = { channels ->
|
bleManager.piezoCollector.onMultiChannelComplete = { channels ->
|
||||||
|
// 메인 스레드에서 처리 (BLE 콜백은 Binder 스레드)
|
||||||
|
handler.post {
|
||||||
isScanning = false
|
isScanning = false
|
||||||
scanCount++
|
scanCount++
|
||||||
|
|
||||||
@@ -682,6 +684,7 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
direction = directionHint
|
direction = directionHint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} // handler.post
|
||||||
bleManager.piezoCollector.onLog = { msg -> Log.d("PlacementGuide", msg) }
|
bleManager.piezoCollector.onLog = { msg -> Log.d("PlacementGuide", msg) }
|
||||||
if (!bleManager.sendAllChannels()) {
|
if (!bleManager.sendAllChannels()) {
|
||||||
// Throttle됨 — isScanning 리셋 (deadlock 방지)
|
// Throttle됨 — isScanning 리셋 (deadlock 방지)
|
||||||
|
|||||||
Reference in New Issue
Block a user