如何在@HiltViewModel中运行语音识别Intent并实现功能?
能否在@HiltViewModel中实现语音识别功能?
问题背景
之前在MainActivity.kt中用resultLauncher实现的语音识别可正常运行,尝试迁移到带@HiltViewModel注解的SettingsViewModel中后代码无响应,想确认是否可行及正确实现方式。
结论:可以实现,但需注意架构规范与细节问题
ViewModel可以处理语音识别的业务逻辑,但不能直接在ViewModel中启动Activity Intent或调用registerForActivityResult——这些操作依赖Activity的上下文与生命周期。你的代码无响应主要有三个原因:
- 未通过Hilt注入
Application,导致application.applicationContext无效 SpeechRecognizer在ViewModel属性初始化阶段就创建,可能因生命周期不匹配导致服务绑定失败- Intent中重复设置
EXTRA_LANGUAGE_MODEL参数,造成配置冲突
两种可行实现方案
方案一:沿用Activity Result API(推荐,符合Jetpack架构)
将Intent启动逻辑留在Activity/Fragment中,ViewModel仅负责处理识别结果与业务逻辑,分工更清晰。
Activity/Fragment代码:
class MainActivity : AppCompatActivity() { private val viewModel: SettingsViewModel by viewModels() private val speechResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) { val speechText = result.data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.get(0) ?: "" viewModel.reactToSpeech(speechText.lowercase()) } } fun startSpeechRecognition() { if (!SpeechRecognizer.isRecognitionAvailable(this)) { Toast.makeText(this, "语音识别不可用", Toast.LENGTH_SHORT).show() return } val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH) putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault()) putExtra(RecognizerIntent.EXTRA_PROMPT, "请说话") } speechResultLauncher.launch(intent) } }
ViewModel代码:
@HiltViewModel class SettingsViewModel @Inject constructor( private val settingsRepository: SettingsRepository ) : ViewModel() { data class SpeechState( val spokenText: String = "", val error: String = "" ) private val _speechState = MutableStateFlow(SpeechState()) val speechState: StateFlow<SpeechState> = _speechState.asStateFlow() fun reactToSpeech(speech: String) { _speechState.value = _speechState.value.copy(spokenText = speech) when(speech) { "run" -> Log.w("App", "Running!") "stop" -> Log.w("App", "Stopped!") else -> {} } } }
方案二:直接在ViewModel中使用SpeechRecognizer API
适合需要更细粒度控制识别过程的场景,但需正确处理上下文与资源释放。
ViewModel代码:
@HiltViewModel class SettingsViewModel @Inject constructor( private val settingsRepository: SettingsRepository, private val application: Application // 通过Hilt注入Application获取上下文 ) : ViewModel(), RecognitionListener { data class SpeechState( val spokenText: String = "", val error: String = "" ) private val _speechState = MutableStateFlow(SpeechState()) val speechState: StateFlow<SpeechState> = _speechState.asStateFlow() private var speechRecognizer: SpeechRecognizer? = null private val recognizerIntent: Intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH) putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault()) putExtra(RecognizerIntent.EXTRA_PROMPT, "请说话") putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, application.packageName) } fun startListening() { if (!SpeechRecognizer.isRecognitionAvailable(application)) { _speechState.value = _speechState.value.copy(error = "语音识别不可用") return } // 延迟初始化,避免ViewModel创建时过早绑定服务 if (speechRecognizer == null) { speechRecognizer = SpeechRecognizer.createSpeechRecognizer(application).apply { setRecognitionListener(this@SettingsViewModel) } } speechRecognizer?.startListening(recognizerIntent) } private fun updateResults(speechBundle: Bundle?) { val userSaid = speechBundle?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION) val text = userSaid?.get(0) ?: "" _speechState.value = _speechState.value.copy(spokenText = text) reactToSpeech(text) } override fun onResults(results: Bundle?) = updateResults(results) override fun onPartialResults(results: Bundle?) = updateResults(results) override fun onError(errorCode: Int) { val errorMsg = when(errorCode) { SpeechRecognizer.ERROR_NO_MATCH -> "未识别到语音" SpeechRecognizer.ERROR_NETWORK -> "网络错误" else -> "识别错误:$errorCode" } _speechState.value = _speechState.value.copy(error = errorMsg) } override fun onEndOfSpeech() { speechRecognizer?.stopListening() } // 其他RecognitionListener空实现 override fun onReadyForSpeech(p0: Bundle?) {} override fun onBeginningOfSpeech() {} override fun onRmsChanged(p0: Float) {} override fun onBufferReceived(p0: ByteArray?) {} override fun onEvent(p0: Int, p1: Bundle?) {} private fun reactToSpeech(speech: String) { when(speech.lowercase()) { "run" -> Log.w("App", "Running!") "stop" -> Log.w("App", "Stopped!") else -> {} } } override fun onCleared() { super.onCleared() // 释放资源,防止内存泄漏 speechRecognizer?.destroy() speechRecognizer = null } }
Activity/Fragment调用:
// 在Activity/Fragment中触发识别 viewModel.startListening()
内容的提问来源于stack exchange,提问作者Alix Blaine
相关产品推荐
相关产品推荐

