refactor(AsrHelper): 优化语音识别连接重连机制

- 移除未使用的 import 语句
- 添加指数退避算法计算重连延迟时间
- 引入最大重连尝试次数限制防止无限重连
- 添加重连状态管理变量控制重连行为
- 实现重连状态重置功能
- 优化网络可用时的重连逻辑
- 改进连接失败时的状态清理
- 添加详细的重连日志记录
这个提交包含在:
徐勤民 2026-04-20 17:02:54 +08:00
父节点 292a4352ac
当前提交 3e4198aa7c

查看文件

@ -1,6 +1,5 @@
package com.nova.brain.glass.helper package com.nova.brain.glass.helper
import android.R.attr.action
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -13,6 +12,7 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.core.graphics.toColorInt
import com.blankj.utilcode.util.Utils import com.blankj.utilcode.util.Utils
import com.nova.brain.glass.BuildConfig import com.nova.brain.glass.BuildConfig
import com.nova.brain.glass.R import com.nova.brain.glass.R
@ -25,9 +25,7 @@ import com.rokid.online.speech.TtsClient
import com.rokid.online.speech.open.AndroidPcmTtsStreamPlayer import com.rokid.online.speech.open.AndroidPcmTtsStreamPlayer
import com.rokid.online.speech.open.OpenSdkAudioSource import com.rokid.online.speech.open.OpenSdkAudioSource
import com.xuqm.base.common.AppManager import com.xuqm.base.common.AppManager
import com.xuqm.base.extensions.showMessage
import kotlin.system.exitProcess import kotlin.system.exitProcess
import androidx.core.graphics.toColorInt
object AsrHelper : OfflineCmdListener { object AsrHelper : OfflineCmdListener {
@ -60,12 +58,18 @@ object AsrHelper : OfflineCmdListener {
private var isTtsConnecting = false private var isTtsConnecting = false
private var pendingStartMic = false private var pendingStartMic = false
private var isClosed = false private var isClosed = false
private var asrReconnectAttempt = 0
private var ttsReconnectAttempt = 0
private var asrReconnectPaused = false
private var ttsReconnectPaused = false
private var connectivityManager: ConnectivityManager? = null private var connectivityManager: ConnectivityManager? = null
private var networkCallbackRegistered = false private var networkCallbackRegistered = false
private const val WAKE_RESPONSE = "在呢,您请说" private const val WAKE_RESPONSE = "在呢,您请说"
private const val LISTENING_TIMEOUT_MS = 30_000L private const val LISTENING_TIMEOUT_MS = 30_000L
private const val RECONNECT_DELAY_MS = 3_000L private const val BASE_RECONNECT_DELAY_MS = 3_000L
private const val MAX_RECONNECT_DELAY_MS = 30_000L
private const val MAX_RECONNECT_ATTEMPTS = 5
private val mainHandler = Handler(Looper.getMainLooper()) private val mainHandler = Handler(Looper.getMainLooper())
private var listeningDialog: AlertDialog? = null private var listeningDialog: AlertDialog? = null
@ -100,6 +104,7 @@ object AsrHelper : OfflineCmdListener {
private val networkCallback = object : ConnectivityManager.NetworkCallback() { private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
Log.d(TAG, "Network available, reconnect speech channels if needed") Log.d(TAG, "Network available, reconnect speech channels if needed")
resetReconnectState()
reconnectAllIfNeeded(immediate = true) reconnectAllIfNeeded(immediate = true)
dismissNoNetworkDialog() dismissNoNetworkDialog()
} }
@ -161,6 +166,7 @@ object AsrHelper : OfflineCmdListener {
registerNetworkCallback() registerNetworkCallback()
// 自动建立 ASR / TTS 连接 // 自动建立 ASR / TTS 连接
resetReconnectState()
asrConnect() asrConnect()
ttsConnect() ttsConnect()
@ -197,6 +203,9 @@ object AsrHelper : OfflineCmdListener {
private fun asrStartMic() { private fun asrStartMic() {
if (!isConnected) { if (!isConnected) {
if (asrReconnectPaused) {
resetAsrReconnectState()
}
pendingStartMic = true pendingStartMic = true
asrConnect() asrConnect()
Log.w(TAG, "ASR not connected, queue startMic after reconnect") Log.w(TAG, "ASR not connected, queue startMic after reconnect")
@ -224,23 +233,38 @@ object AsrHelper : OfflineCmdListener {
mainHandler.removeCallbacks(listeningTimeoutRunnable) mainHandler.removeCallbacks(listeningTimeoutRunnable)
} }
private fun scheduleAsrReconnect(delayMs: Long = RECONNECT_DELAY_MS) { private fun scheduleAsrReconnect(delayMs: Long? = null) {
if (!shouldReconnectAsr()) return if (!shouldReconnectAsr()) return
if (asrReconnectAttempt >= MAX_RECONNECT_ATTEMPTS) {
asrReconnectPaused = true
pendingStartMic = false
Log.w(TAG, "ASR reconnect paused after $MAX_RECONNECT_ATTEMPTS failures")
return
}
asrReconnectAttempt += 1
val actualDelayMs = delayMs ?: calculateReconnectDelay(asrReconnectAttempt)
mainHandler.removeCallbacks(asrReconnectRunnable) mainHandler.removeCallbacks(asrReconnectRunnable)
mainHandler.postDelayed(asrReconnectRunnable, delayMs) mainHandler.postDelayed(asrReconnectRunnable, actualDelayMs)
Log.d(TAG, "ASR reconnect scheduled in ${delayMs}ms") Log.d(TAG, "ASR reconnect scheduled in ${actualDelayMs}ms, attempt=$asrReconnectAttempt")
} }
private fun scheduleTtsReconnect(delayMs: Long = RECONNECT_DELAY_MS) { private fun scheduleTtsReconnect(delayMs: Long? = null) {
if (!shouldReconnectTts()) return if (!shouldReconnectTts()) return
if (ttsReconnectAttempt >= MAX_RECONNECT_ATTEMPTS) {
ttsReconnectPaused = true
Log.w(TAG, "TTS reconnect paused after $MAX_RECONNECT_ATTEMPTS failures")
return
}
ttsReconnectAttempt += 1
val actualDelayMs = delayMs ?: calculateReconnectDelay(ttsReconnectAttempt)
mainHandler.removeCallbacks(ttsReconnectRunnable) mainHandler.removeCallbacks(ttsReconnectRunnable)
mainHandler.postDelayed(ttsReconnectRunnable, delayMs) mainHandler.postDelayed(ttsReconnectRunnable, actualDelayMs)
Log.d(TAG, "TTS reconnect scheduled in ${delayMs}ms") Log.d(TAG, "TTS reconnect scheduled in ${actualDelayMs}ms, attempt=$ttsReconnectAttempt")
} }
private fun reconnectAllIfNeeded(immediate: Boolean = false) { private fun reconnectAllIfNeeded(immediate: Boolean = false) {
if (!isNetworkAvailable()) return if (!isNetworkAvailable()) return
val delayMs = if (immediate) 0L else RECONNECT_DELAY_MS val delayMs = if (immediate) 0L else null
if (!isConnected) { if (!isConnected) {
scheduleAsrReconnect(delayMs) scheduleAsrReconnect(delayMs)
} }
@ -250,11 +274,33 @@ object AsrHelper : OfflineCmdListener {
} }
private fun shouldReconnectAsr(): Boolean { private fun shouldReconnectAsr(): Boolean {
return !isClosed && !isConnected && !isAsrConnecting && isNetworkAvailable() return !isClosed && !isConnected && !isAsrConnecting && !asrReconnectPaused && isNetworkAvailable()
} }
private fun shouldReconnectTts(): Boolean { private fun shouldReconnectTts(): Boolean {
return !isClosed && !isTtsConnected && !isTtsConnecting && isNetworkAvailable() return !isClosed && !isTtsConnected && !isTtsConnecting && !ttsReconnectPaused && isNetworkAvailable()
}
private fun calculateReconnectDelay(attempt: Int): Long {
val factor = 1L shl (attempt - 1).coerceAtLeast(0)
return (BASE_RECONNECT_DELAY_MS * factor).coerceAtMost(MAX_RECONNECT_DELAY_MS)
}
private fun resetAsrReconnectState() {
asrReconnectAttempt = 0
asrReconnectPaused = false
mainHandler.removeCallbacks(asrReconnectRunnable)
}
private fun resetTtsReconnectState() {
ttsReconnectAttempt = 0
ttsReconnectPaused = false
mainHandler.removeCallbacks(ttsReconnectRunnable)
}
private fun resetReconnectState() {
resetAsrReconnectState()
resetTtsReconnectState()
} }
private fun getContext(): Context = Utils.getApp() private fun getContext(): Context = Utils.getApp()
@ -377,7 +423,7 @@ object AsrHelper : OfflineCmdListener {
override fun onOpen() { override fun onOpen() {
isConnected = true isConnected = true
isAsrConnecting = false isAsrConnecting = false
mainHandler.removeCallbacks(asrReconnectRunnable) resetAsrReconnectState()
Log.d(TAG, "ASR websocket open") Log.d(TAG, "ASR websocket open")
if (pendingStartMic) { if (pendingStartMic) {
asrStartMic() asrStartMic()
@ -421,7 +467,10 @@ object AsrHelper : OfflineCmdListener {
when (action.name) { when (action.name) {
"goToDecisionCenter" -> onGoToDecisionCenter?.invoke(action) "goToDecisionCenter" -> onGoToDecisionCenter?.invoke(action)
"goToTaskCenter" -> onGoToTaskCenter?.invoke(action) "goToTaskCenter" -> onGoToTaskCenter?.invoke(action)
"openTaskDetail", "openTaskDetailWithFilter" -> onOpenTaskDetail?.invoke(action) "openTaskDetail", "openTaskDetailWithFilter" -> onOpenTaskDetail?.invoke(
action
)
else -> Log.d(TAG, "unhandled action: $action") else -> Log.d(TAG, "unhandled action: $action")
} }
}, },
@ -438,6 +487,10 @@ object AsrHelper : OfflineCmdListener {
isConnected = false isConnected = false
isAsrConnecting = false isAsrConnecting = false
isMicRunning = false isMicRunning = false
if (pendingStartMic) {
Log.w(TAG, "ASR pending startMic cleared after websocket failure")
}
pendingStartMic = false
dismissListeningDialog() dismissListeningDialog()
scheduleAsrReconnect() scheduleAsrReconnect()
} }
@ -446,6 +499,7 @@ object AsrHelper : OfflineCmdListener {
isConnected = false isConnected = false
isAsrConnecting = false isAsrConnecting = false
isMicRunning = false isMicRunning = false
pendingStartMic = false
dismissListeningDialog() dismissListeningDialog()
Log.d(TAG, "ASR closed code=$code reason=$reason") Log.d(TAG, "ASR closed code=$code reason=$reason")
scheduleAsrReconnect() scheduleAsrReconnect()
@ -458,7 +512,7 @@ object AsrHelper : OfflineCmdListener {
override fun onOpen() { override fun onOpen() {
isTtsConnected = true isTtsConnected = true
isTtsConnecting = false isTtsConnecting = false
mainHandler.removeCallbacks(ttsReconnectRunnable) resetTtsReconnectState()
Log.d(TAG, "TTS websocket open") Log.d(TAG, "TTS websocket open")
} }
@ -470,7 +524,7 @@ object AsrHelper : OfflineCmdListener {
override fun onError(code: Int, message: String) { override fun onError(code: Int, message: String) {
isTtsConnected = false isTtsConnected = false
isTtsConnecting = false isTtsConnecting = false
Log.e(TAG, "TTS error code=$code msg=$message, fallback to mic") Log.e(TAG, "TTS error code=$code msg=$message")
scheduleTtsReconnect() scheduleTtsReconnect()
asrStartMic() asrStartMic()
} }
@ -497,6 +551,7 @@ object AsrHelper : OfflineCmdListener {
showListeningDialog() showListeningDialog()
restartListeningTimeout() restartListeningTimeout()
pendingStartMic = true pendingStartMic = true
resetReconnectState()
if (isTtsConnected) { if (isTtsConnected) {
tts?.speak(WAKE_RESPONSE) tts?.speak(WAKE_RESPONSE)
} else { } else {
@ -519,6 +574,7 @@ object AsrHelper : OfflineCmdListener {
activity.finishAffinity() activity.finishAffinity()
exitProcess(0) exitProcess(0)
} }
"返回", "退回" -> { "返回", "退回" -> {
activity.startActivity( activity.startActivity(
Intent(activity, WelcomeActivity::class.java).apply { Intent(activity, WelcomeActivity::class.java).apply {
@ -556,6 +612,11 @@ object AsrHelper : OfflineCmdListener {
isAsrConnecting = false isAsrConnecting = false
isTtsConnected = false isTtsConnected = false
isTtsConnecting = false isTtsConnecting = false
asrReconnectAttempt = 0
ttsReconnectAttempt = 0
asrReconnectPaused = false
ttsReconnectPaused = false
pendingStartMic = false
Log.d(TAG, "AsrHelper closed") Log.d(TAG, "AsrHelper closed")
} }
} }