You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 14:55:10