From 2061abb8600bd1e53547108fe3d0a1507354d09f Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Tue, 6 Jan 2026 14:05:30 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=A2=91=E7=B9=81=E6=8C=89?= =?UTF-8?q?=E7=9D=80=E6=9D=BE=E5=BC=80=E5=A5=94=E6=BA=83=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../speech_impl/azure_asr_service.dart | 9 +++------ .../agent_service/lib/agent_service.dart | 18 ++++++++++-------- .../tools/AudioRecordingForegroundService.kt | 17 ++++++++++++++--- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/lib/data/services/speech_impl/azure_asr_service.dart b/lib/data/services/speech_impl/azure_asr_service.dart index e3d66e411..a196a5ddc 100644 --- a/lib/data/services/speech_impl/azure_asr_service.dart +++ b/lib/data/services/speech_impl/azure_asr_service.dart @@ -252,12 +252,9 @@ class AzureAsrService extends GetxService implements AsrService { /// - 若当前不在识别中,直接返回 true; /// - 调用成功后立即将内部状态置为 false(同时事件通道回调仍会再次校正)。 Future stopContinuousRecognition() async { - // 防抖判断:短时间内重复调用直接拦截 - final now = DateTime.now(); - if (_lastStopAt != null && now.difference(_lastStopAt!) < _debounce) { - return false; - } - _lastStopAt = now; + // 移除防抖判断,确保停止操作总是被执行 + // 这与 AgentService 的逻辑保持一致,防止“松开还在录”的问题 + _lastStopAt = DateTime.now(); if (!_isInitialized || !_isContinuousRecognitionActive) { return true; diff --git a/local_plugins/agent_service/lib/agent_service.dart b/local_plugins/agent_service/lib/agent_service.dart index 6d1017ae9..bccb5db71 100644 --- a/local_plugins/agent_service/lib/agent_service.dart +++ b/local_plugins/agent_service/lib/agent_service.dart @@ -258,11 +258,17 @@ class AgentService { if (_lastStartConversationAt != null && now.difference(_lastStartConversationAt!) < _conversationDebounce) { // 函数级注释:当在冷却时间内再次调用时,直接返回 false 表示此次调用被防抖拦截 + // 对于启动操作,如果被防抖拦截,意味着用户点击太快。 + // 返回 false 是正确的,因为我们确实没有启动新的会话。 return false; } _lastStartConversationAt = now; try { + // 在启动新对话前,确保之前的对话已完全停止 + // 这不仅可以清理状态,还能避免因快速切换导致的资源冲突 + await stopConversation(); + final bool result = await _channel.invokeMethod('startConversation', { 'mode': mode, }); @@ -328,14 +334,10 @@ class AgentService { /// 停止语音识别 /// 返回是否成功停止对话 static Future stopConversation() async { - // 防抖:短时间内重复点击直接忽略(冷却式防抖) - final now = DateTime.now(); - if (_lastStopConversationAt != null && - now.difference(_lastStopConversationAt!) < _conversationDebounce) { - // 函数级注释:当在冷却时间内再次调用时,直接返回 false 表示此次调用被防抖拦截 - return false; - } - _lastStopConversationAt = now; + // 移除防抖拦截! + // 停止操作是安全且必要的,不应该被拦截。 + // 即使频繁调用,Native 层也能处理(我们已经修复了 Native 层的并发问题)。 + _lastStopConversationAt = DateTime.now(); try { final bool result = await _channel.invokeMethod('stopConversation'); diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt index 4b03f4ec3..0b1d1b1fb 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt @@ -23,6 +23,7 @@ class AudioRecordingForegroundService : Service() { private const val NOTIFICATION_ID = 1001 private const val CHANNEL_ID = "audio_recording_channel" private const val CHANNEL_NAME = "音频录制服务" + private const val ACTION_STOP = "com.yunqiinnovation.azure_speech.tools.STOP_SERVICE" /** * 启动前台服务 @@ -44,8 +45,9 @@ class AudioRecordingForegroundService : Service() { */ fun stopService(context: Context) { val intent = Intent(context, AudioRecordingForegroundService::class.java) - context.stopService(intent) - Log.d(TAG, "音频录制前台服务已停止") + intent.action = ACTION_STOP + context.startService(intent) + Log.d(TAG, "发送停止音频录制前台服务指令") } } @@ -56,12 +58,21 @@ class AudioRecordingForegroundService : Service() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.d(TAG, "AudioRecordingForegroundService onStartCommand") + Log.d(TAG, "AudioRecordingForegroundService onStartCommand action=${intent?.action}") // 创建前台通知 val notification = createNotification() + // 必须立即调用 startForeground 以满足 Android 8.0+ 的要求 startForeground(NOTIFICATION_ID, notification) + // 检查是否是停止指令 + if (intent?.action == ACTION_STOP) { + Log.d(TAG, "收到停止指令,停止服务") + stopForeground(true) + stopSelf() + return START_NOT_STICKY + } + // 返回START_STICKY确保服务被系统杀死后会重启 return START_STICKY }