From de1ac10c5fbe13f14b4057a8fb01941f40007c7c Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Tue, 9 Sep 2025 18:40:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=89=E5=8D=93=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=88=B0=E5=90=8E=E5=8F=B0=E4=B8=8D=E8=83=BD=E7=BB=A7?= =?UTF-8?q?=E7=BB=AD=E5=BD=95=E9=9F=B3=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/src/main/AndroidManifest.xml | 15 ++- .../tools/AudioRecordingForegroundService.kt | 125 ++++++++++++++++++ .../azure_speech/tools/SimpleAudioReceiver.kt | 16 ++- 3 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt diff --git a/local_plugins/azure_speech/android/src/main/AndroidManifest.xml b/local_plugins/azure_speech/android/src/main/AndroidManifest.xml index df2a770c6..88f2910c1 100644 --- a/local_plugins/azure_speech/android/src/main/AndroidManifest.xml +++ b/local_plugins/azure_speech/android/src/main/AndroidManifest.xml @@ -3,4 +3,17 @@ package="com.yunqiinnovation.azure_speech"> - \ No newline at end of file + + + + + + + + + + \ No newline at end of file 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 new file mode 100644 index 000000000..4b03f4ec3 --- /dev/null +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt @@ -0,0 +1,125 @@ +package com.yunqiinnovation.azure_speech.tools + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.IBinder +import androidx.core.app.NotificationCompat +import android.util.Log + +/** + * 音频录制前台服务 + * 用于在录制音频时保持应用在前台运行,防止系统杀死应用 + */ +class AudioRecordingForegroundService : Service() { + + companion object { + private const val TAG = "AudioRecordingService" + private const val NOTIFICATION_ID = 1001 + private const val CHANNEL_ID = "audio_recording_channel" + private const val CHANNEL_NAME = "音频录制服务" + + /** + * 启动前台服务 + * @param context 上下文 + */ + fun startService(context: Context) { + val intent = Intent(context, AudioRecordingForegroundService::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent) + } else { + context.startService(intent) + } + Log.d(TAG, "音频录制前台服务已启动") + } + + /** + * 停止前台服务 + * @param context 上下文 + */ + fun stopService(context: Context) { + val intent = Intent(context, AudioRecordingForegroundService::class.java) + context.stopService(intent) + Log.d(TAG, "音频录制前台服务已停止") + } + } + + override fun onCreate() { + super.onCreate() + Log.d(TAG, "AudioRecordingForegroundService onCreate") + createNotificationChannel() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.d(TAG, "AudioRecordingForegroundService onStartCommand") + + // 创建前台通知 + val notification = createNotification() + startForeground(NOTIFICATION_ID, notification) + + // 返回START_STICKY确保服务被系统杀死后会重启 + return START_STICKY + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } + + override fun onDestroy() { + super.onDestroy() + Log.d(TAG, "AudioRecordingForegroundService onDestroy") + stopForeground(true) + } + + /** + * 创建通知渠道(Android 8.0及以上需要) + */ + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + CHANNEL_ID, + CHANNEL_NAME, + NotificationManager.IMPORTANCE_LOW + ).apply { + description = "用于音频录制时保持应用运行" + setShowBadge(false) + setSound(null, null) + enableVibration(false) + } + + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } + } + + /** + * 创建前台服务通知 + * @return 通知对象 + */ + private fun createNotification(): Notification { + // 创建点击通知时的意图(可以打开应用主界面) + val intent = packageManager.getLaunchIntentForPackage(packageName) + val pendingIntent = PendingIntent.getActivity( + this, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + return NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle("语音助手正在运行") + .setContentText("正在录制音频,点击返回应用") + .setSmallIcon(android.R.drawable.ic_btn_speak_now) // 使用系统默认麦克风图标 + .setContentIntent(pendingIntent) + .setOngoing(true) // 设置为持续通知,用户无法滑动删除 + .setPriority(NotificationCompat.PRIORITY_LOW) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setShowWhen(false) + .build() + } +} \ No newline at end of file diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt index 08e5ae668..ee4e9fed0 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt @@ -17,6 +17,7 @@ import android.media.AudioFocusRequest import android.media.AudioAttributes import com.yunqiinnovation.azure_speech.tools.RecordFile import android.os.Build // 新增:用于获取设备信息 +import com.yunqiinnovation.azure_speech.tools.AudioRecordingForegroundService /** * 简单音频接收器类,用于处理音频录制和流传输 @@ -208,6 +209,10 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba Log.d("tag", "存下来的数据: ${writeQueue?.size}") } + // 启动前台服务 + AudioRecordingForegroundService.startService(context) + Log.d(TAG, "前台服务已启动,开始音频录制") + when (audioSourceType) { AudioSourceType.MICROPHONE -> runMicrophoneCapture() AudioSourceType.EXTERNAL -> runExternalCapture() @@ -218,10 +223,11 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba pushAudioStream?.close() pushAudioStream = null isWriting.set(false) + // 停止前台服务 + AudioRecordingForegroundService.stopService(context) throw e // 重新抛出异常让调用者处理 } } -// ... existing code ... /** * 配置通话音频模式以优化回声消除 */ @@ -392,6 +398,9 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba try { writeQueue.clear() isWriting.set(false) + // 停止前台服务 + AudioRecordingForegroundService.stopService(context) + // 停止录音 audioRecord?.stop() @@ -403,7 +412,6 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba Log.e(TAG, "停止麦克风捕获失败: ${e.message}") } } - /** * 释放音频资源 */ @@ -411,7 +419,9 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba try { isRunning.set(false) isWriting.set(false) - + // 停止前台服务 + AudioRecordingForegroundService.stopService(context) + Log.d(TAG, "前台服务已停止,音频资源已释放") // 中断并等待捕获线程结束 writeThread?.interrupt() writeThread?.join(300) // 最多等待300ms