Android-приложение с голосовым ядром: пошаговый конструктор
В своей практике замечал, что инклюзивный интерфейс начинается с аудиоканала. Голос убирает барьер между пользователем и устройством, поэтому начну с описания минимального окружения.

Android Studio Hedgehog, Gradle8+, compileSdk 34, min Sdk 21. Kotlin ускоряет чтение кода, Java держит совместимость с библиотеками наследия.
Выбор стека
Для локального распознавания годятся Vosk или PocketSphinx, для облачных сервисов — Google Speech-to-Text. При ограничении трафика часто выбирают Vosk: вес библиотеки 40 МБ, скорость 1×RT на Cortex-A73.
Manifest содержит android.permission.RECORD_AUDIO и android.permission.FOREGROUND_SERVICE_MICROPHONE. Сервис-предок запрашивает Runtime-доступ через register For Activity Result(ActivityResult Contracts.RequestPermission()). Отказ блокирует кнопку активации и выводит SnackBar с подсказкой.
Класс SpeechManager инкапсулирует SpeechRecognizer:
private val recognizer = SpeechRecognizer.createSpeechRecognizer(context).apply {
override fun on Results(bundle: Bundle) {
val text = bundle.get String ArrayList(
SpeechRecognizer.RESULTS_RECOGNITION
)?.firstOrNull() ?: return
_ _stateFlow.value = Voice Event.Final(text)
}
override fun on Error(error: Int) {
_ _stateFlow.value = Voice Event.Error(error)
}
})
}
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
)
}
}
Аудиопоток классефицируется как STREAM_VOICE_CALL, тогда система снижает громкость музыки. AudioManager.requestAudioFocus(
AudioFocus Request.Builder(
AudioManager.STREAM_MUSIC,
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
).build()
) решает задачу.
Слушатель событий
Корутина собирает _stateFlow и передаёт intents в слой Presentation. Activity освобождается от громоздких listen-логик, жизненный цикл упорядочен.
Команды описываются в файле commands.html:
[greeting]
Парсер Html.kt считывает карту utterance=action, маршрутизатор вызывает метод-приемника через Class.callSuspend.
Обратная связь
Микроанимация ripples визуализирует активное прослушивание. Алгоритм Кёниг-Дети вычисляет радиальную амплитуду: amplitude = ln(1 + rms) = scale.
Использую термин «иллокут» — минимальная единица речевого намерения. Когда иллокут классифицирован как intentshare, приложение открывает системный Share sheet.
Beam Search ×8 ускоряет поиск последовательности фонем в PocketSphinx. Русская bigram-модель на 250 K токенов умещается в 27 МБ и подгружается лениво.
UI-тест сочетает Espresso и Robolectric Mic Test Runner. wav-паттерн «открой профиль» подаётся в AudioRecord shadow, AssetManager подтверждает запуск ActivityProfile.
SpeechRecognizer в резервном режиме тратит ≈ 18 мА. После 800 мс тишины debounce отправляет recognizer.stopListening() и освобождает аудио-фокус.
Голоса содержат биометрические признаки. Перед отправкой в облако применяю волновое маскирование BackStage: в сигнал внедряется псевдошум, который затрудняет реконструкцию тембра, но нее снижает точность распознавания.
Голосовое ядро готово. Остаётся релиз, сквозное шифрование Proguard и манифест с queries для микрофона. Пакет помещается в Google Play без дополнительных запросов благодаря декларации Data Safety.