From e0405b541f14fcdd4fc11ec883a74e1ff778c431 Mon Sep 17 00:00:00 2001 From: QIU Date: Thu, 13 Aug 2026 03:16:36 +0000 Subject: [PATCH] fix(cw): prevent double AudioRecord when toggling capture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CwDecodeScreen 的 LaunchedEffect 原先用内层 launch{} 启动音频采集。 isListening 在权限授予后由 false->true->(授予回调再次置 true), 加上 LaunchedEffect(isListening, permissionGranted) 的双 key 重启, 会造成 前一个 audioFlow().collect 尚未取消、新的又启动 —— 两个 AudioRecord 同时录音, 内存翻倍、音频混叠, 是低内存机 OOM 崩溃的实锤级嫌疑。 改为在 LaunchedEffect 协程体内直接 collect (取消时随父协程及时停止), 并保持 (isListening, permissionGranted) 双 key 以正确处理权限授予后 需要重启采集的情形。 同时清理不再使用的 kotlinx.coroutines.launch import。 验证: :feature:cw:compileDebugKotlin => BUILD SUCCESSFUL --- .../look4sat/feature/cw/CwDecodeScreen.kt | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/feature/cw/src/main/java/com/rtbishop/look4sat/feature/cw/CwDecodeScreen.kt b/feature/cw/src/main/java/com/rtbishop/look4sat/feature/cw/CwDecodeScreen.kt index c670a18b..c5264d88 100644 --- a/feature/cw/src/main/java/com/rtbishop/look4sat/feature/cw/CwDecodeScreen.kt +++ b/feature/cw/src/main/java/com/rtbishop/look4sat/feature/cw/CwDecodeScreen.kt @@ -65,7 +65,6 @@ import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat import com.rtbishop.look4sat.core.domain.repository.IContainerProvider import com.rtbishop.look4sat.core.presentation.R as CoreR -import kotlinx.coroutines.launch /** * Full-page CW decoder backed by DeepCW. @@ -110,19 +109,19 @@ fun CwDecodeScreen(navigateUp: () -> Unit = {}) { if (granted) isListening = true } - // Capture runs only while listening; cancelling the effect stops the mic. + // Capture runs only while listening. Keyed on both listening state and + // permission so granting the permission mid-flow restarts collection + // (isListening may already be true when the launcher returns). LaunchedEffect(isListening, permissionGranted) { - if (!isListening || !permissionGranted) return@LaunchedEffect - launch { - audioCapture.audioFlow().collect { chunk -> - decoder.processBuffer(chunk, audioCapture.sampleRate) - waterfall.pushSamples(chunk, audioCapture.sampleRate) - } + if (!isListening) return@LaunchedEffect + if (!permissionGranted) { + permissionLauncher.launch(Manifest.permission.RECORD_AUDIO) + return@LaunchedEffect + } + audioCapture.audioFlow().collect { chunk -> + decoder.processBuffer(chunk, audioCapture.sampleRate) + waterfall.pushSamples(chunk, audioCapture.sampleRate) } - } - - LaunchedEffect(permissionGranted) { - if (permissionGranted) isListening = true } DisposableEffect(Unit) {