Android无障碍通话应用开发:集成TTS与语音识别实现双向通信
在实际开发中我们常常会遇到需要为特殊群体设计无障碍交互的场景。例如对于因疾病、手术或先天原因导致暂时或永久性言语障碍的用户如何接打电话是一个真实且急迫的需求。传统的文字转语音TTS和语音识别技术虽然成熟但将它们无缝集成到一个稳定、易用、且能应对复杂通话流程的移动应用中仍涉及一系列工程挑战包括音频处理、实时交互、界面适配和网络兼容性等。本文将以一个“为言语障碍者设计的电话辅助工具”为技术主线从零开始探讨如何构建一个具备核心通话功能的 Android 应用。我们将使用 Kotlin 语言集成 Android 原生 TTS 引擎和 Google 的 Speech-to-Text API实现“文本输入 - 语音播报给对方”以及“收听对方语音 - 实时转文字显示”的双向通信闭环。文章将详细拆解项目结构、关键代码、权限处理、音频焦点管理以及实际测试中遇到的典型问题目标是交付一个可学习、可复现、可扩展的技术方案。1. 理解核心交互模型与关键技术选型在开始编码前必须明确我们要解决的核心问题是什么以及现有技术如何组合来解决它。这个工具不是简单的录音播放器而是一个在标准电话通话链路中插入的实时辅助层。1.1 交互流程拆解一次完整的辅助通话涉及两个角色使用本工具的用户发送方和接听电话的对方接收方。流程如下发送方接听/拨打电话工具作为前台服务运行监听电话状态。发送方输入文本在通话建立后发送方通过键盘或预置短语输入想说的话。文本转语音TTS播报工具将输入文本转换为语音并通过手机听筒或扬声器播放声音会通过标准通话音频链路传输给接收方。接收方说话接收方像正常通话一样进行语音回复。语音转文字STT显示工具通过手机麦克风捕获接收方的语音将其转换为文字并实时显示在发送方的设备屏幕上。这个流程的关键在于TTS 输出和 STT 输入必须与系统电话通话共享同一个音频通道且不能相互干扰。1.2 技术栈选择与考量开发语言与平台选择 Kotlin 和 Android 原生开发。原因在于需要深度调用系统电话状态、音频管理 API以及需要稳定的前台服务保活跨平台框架在此类底层交互上可能受限或增加复杂度。文本转语音TTS使用 Android 原生的TextToSpeech引擎。它免费、离线可用需下载语音数据包且与系统集成度高能较好地处理通话中的音频焦点冲突。我们不需要依赖第三方在线 TTS 服务以保障无网络环境下的基础功能。语音转文字STT选择 Google 的SpeechRecognizerAPI需 Google 服务支持。虽然它需要网络连接但识别准确率高、延迟相对较低并且免费额度通常足够个人使用。对于完全离线的场景可考虑集成如Mozilla DeepSpeech等离线模型但那会显著增加应用体积和复杂度本文作为核心教程优先使用官方 API 实现可行性。通话状态监听使用TelephonyManager和PhoneStateListener来监听来电、去电、通话中等状态。音频管理使用AudioManager来请求和释放音频焦点确保 TTS 播报时系统能正确处理通话音频和媒体音频的关系。2. 环境准备与项目基础配置在 Android Studio 中创建一个新的项目开始进行基础配置。2.1 开发环境与依赖确保你的环境满足以下要求Android Studio最新稳定版如 Flamingo 或 Giraffe。JDK17 或以上。目标设备/模拟器建议使用 Android 9.0 (API level 28) 或更高版本以获得更稳定的 TTS 和 STT API 支持。在app/build.gradle.kts(或app/build.gradle) 文件中配置最低 SDK 并添加必要的依赖。// app/build.gradle.kts android { namespace com.example.assistivecall compileSdk 34 defaultConfig { applicationId com.example.assistivecall minSdk 28 // 设置为 API 28确保较好的兼容性 targetSdk 34 versionCode 1 versionName 1.0 } // ... 其他配置 } dependencies { implementation(androidx.core:core-ktx:1.12.0) implementation(androidx.lifecycle:lifecycle-runtime-ktx:2.7.0) implementation(androidx.activity:activity-compose:1.8.2) // 如果需要使用 Compose UI implementation(platform(androidx.compose:compose-bom:2023.10.01)) implementation(androidx.compose.ui:ui) implementation(androidx.compose.ui:ui-graphics) implementation(androidx.compose.ui:ui-tooling-preview) implementation(androidx.compose.material3:material3) // 用于前台服务等 implementation(androidx.lifecycle:lifecycle-service:2.7.0) }2.2 权限声明此应用需要一系列敏感权限必须在AndroidManifest.xml中声明并在运行时动态申请针对 Android 6.0。!-- app/src/main/AndroidManifest.xml -- manifest xmlns:androidhttp://schemas.android.com/apk/res/android !-- 网络权限用于在线语音识别 -- uses-permission android:nameandroid.permission.INTERNET / !-- 录音权限用于捕获对方语音进行识别 -- uses-permission android:nameandroid.permission.RECORD_AUDIO / !-- 读取电话状态权限用于监听来电、去电 -- uses-permission android:nameandroid.permission.READ_PHONE_STATE / !-- 前台服务权限用于在通话时保持应用活跃 -- uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / !-- 如果目标API33需要新增的录音权限 -- uses-permission android:nameandroid.permission.RECORD_AUDIO / !-- 在API 33还需要声明音频媒体权限非必须但建议 -- uses-permission android:nameandroid.permission.POST_NOTIFICATIONS / application android:allowBackuptrue android:iconmipmap/ic_launcher android:labelstring/app_name android:themestyle/Theme.AssistiveCall !-- 主Activity -- activity android:name.MainActivity android:exportedtrue intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity !-- 通话辅助服务 -- service android:name.CallAssistService android:enabledtrue android:exportedfalse android:foregroundServiceTypephoneCall / !-- API 31 需要指定类型 -- /application /manifest注意RECORD_AUDIO和READ_PHONE_STATE属于危险权限必须在 Activity 中向用户动态申请。FOREGROUND_SERVICE和POST_NOTIFICATIONS在对应高版本系统上也需动态申请。3. 构建核心通话辅助服务 (CallAssistService)核心逻辑应该放在一个Service中因为它需要在后台持续运行监听电话状态并管理音频。我们将其实现为一个LifecycleService以便更好地与组件生命周期协同。3.1 服务骨架与电话状态监听首先创建CallAssistService类。// app/src/main/java/com/example/assistivecall/CallAssistService.kt import android.app.* import android.content.Context import android.content.Intent import android.os.* import android.telephony.TelephonyManager import androidx.lifecycle.LifecycleService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch class CallAssistService : LifecycleService() { private val serviceScope CoroutineScope(SupervisorJob() Dispatchers.Main) private lateinit var telephonyManager: TelephonyManager private var phoneStateListener: PhoneStateListener? null override fun onCreate() { super.onCreate() // 初始化电话管理器 telephonyManager getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager startForegroundService() setupPhoneStateListener() } private fun startForegroundService() { // 创建通知渠道 (API 26) if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { val channel NotificationChannel( CHANNEL_ID, 通话辅助服务, NotificationManager.IMPORTANCE_LOW ).apply { description 用于在通话中提供语音转文字和文字转语音辅助 } (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager) .createNotificationChannel(channel) } // 创建前台服务通知 val notification Notification.Builder(this, CHANNEL_ID) .setContentTitle(通话辅助工具运行中) .setContentText(已就绪可接听/拨打电话) .setSmallIcon(android.R.drawable.ic_dialog_info) // 替换为你自己的图标 .build() // 启动前台服务。Android 9 需要 FOREGROUND_SERVICE 权限。 // Android 12 需要在 manifest 中指定 foregroundServiceType。 startForeground(NOTIFICATION_ID, notification) } private fun setupPhoneStateListener() { phoneStateListener object : PhoneStateListener() { override fun onCallStateChanged(state: Int, phoneNumber: String?) { when (state) { TelephonyManager.CALL_STATE_IDLE - { // 通话结束 serviceScope.launch { onCallEnded() } } TelephonyManager.CALL_STATE_OFFHOOK - { // 通话接通包括拨出接通和接听 serviceScope.launch { onCallConnected() } } TelephonyManager.CALL_STATE_RINGING - { // 来电响铃 serviceScope.launch { onIncomingCall(phoneNumber) } } } } } // 注册监听器监听通话状态变化 telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE) } private suspend fun onCallConnected() { // 通话接通初始化 TTS 和 STT并开始监听对方语音 initTTS() startSpeechRecognition() } private suspend fun onCallEnded() { // 通话结束释放资源 releaseTTS() stopSpeechRecognition() } private suspend fun onIncomingCall(phoneNumber: String?) { // 可以在这里显示来电号码或者预加载一些常用回复短语 // 本例中仅作日志记录 android.util.Log.d(TAG, Incoming call from: $phoneNumber) } override fun onDestroy() { super.onDestroy() phoneStateListener?.let { telephonyManager.listen(it, PhoneStateListener.LISTEN_NONE) } serviceScope.cancel() } companion object { private const val TAG CallAssistService private const val CHANNEL_ID call_assist_channel private const val NOTIFICATION_ID 1 } }3.2 集成文本转语音 (TTS)在服务中初始化TextToSpeech并处理音频焦点。// 在 CallAssistService 类中添加以下成员和方法 import android.speech.tts.TextToSpeech import android.speech.tts.UtteranceProgressListener import android.media.AudioManager import java.util.* class CallAssistService : LifecycleService() { // ... 已有代码 ... private lateinit var textToSpeech: TextToSpeech private lateinit var audioManager: AudioManager private var isTtsReady false private suspend fun initTTS() { // 在主线程初始化 TTS textToSpeech TextToSpeech(applicationContext) { status - if (status TextToSpeech.SUCCESS) { // 设置语言这里设置为中文 val result textToSpeech.setLanguage(Locale.CHINESE) isTtsReady if (result ! TextToSpeech.LANG_MISSING_DATA result ! TextToSpeech.LANG_NOT_SUPPORTED ) { android.util.Log.d(TAG, TTS 初始化成功) true } else { android.util.Log.e(TAG, TTS 语言不支持或数据缺失) false } } else { android.util.Log.e(TAG, TTS 初始化失败) isTtsReady false } } audioManager getSystemService(Context.AUDIO_SERVICE) as AudioManager } /** * 对外暴露的方法将文本转换为语音并播放给对方 */ fun speakText(text: String) { if (!isTtsReady) { android.util.Log.w(TAG, TTS 未就绪无法播报) return } serviceScope.launch(Dispatchers.Main) { // 1. 请求音频焦点让系统知道我们要播放重要的音频通话语音 val focusResult audioManager.requestAudioFocus( null, // 可以传入 AudioManager.OnAudioFocusChangeListener 来监听焦点变化 AudioManager.STREAM_VOICE_CALL, // 使用通话音频流 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT // 临时获取焦点 ) if (focusResult AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // 2. 设置 TTS 使用通话音频流确保声音从听筒/扬声器进入通话链路 val params Bundle().apply { putInt(TextToSpeech.Engine.KEY_PARAM_STREAM, AudioManager.STREAM_VOICE_CALL) } // 3. 播报语音 textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, params, utteranceId_${System.currentTimeMillis()}) // 4. 设置监听器在播报完成后释放音频焦点 textToSpeech.setOnUtteranceProgressListener(object : UtteranceProgressListener() { override fun onStart(utteranceId: String?) {} override fun onError(utteranceId: String?) { releaseAudioFocus() } override fun onDone(utteranceId: String?) { releaseAudioFocus() } }) } else { android.util.Log.e(TAG, 无法获取音频焦点播报失败) } } } private fun releaseAudioFocus() { audioManager.abandonAudioFocus(null) } private fun releaseTTS() { if (::textToSpeech.isInitialized) { textToSpeech.stop() textToSpeech.shutdown() isTtsReady false } } }3.3 集成语音转文字 (STT)使用SpeechRecognizer持续监听来自麦克风的音频即对方说话的声音。// 在 CallAssistService 类中添加以下成员和方法 import android.speech.SpeechRecognizer import android.speech.RecognitionListener import android.speech.RecognizerIntent import android.content.Intent class CallAssistService : LifecycleService() { // ... 已有代码 ... private lateinit var speechRecognizer: SpeechRecognizer private var isListening false private suspend fun startSpeechRecognition() { // 检查设备是否支持语音识别 if (!SpeechRecognizer.isRecognitionAvailable(applicationContext)) { android.util.Log.e(TAG, 设备不支持语音识别) return } speechRecognizer SpeechRecognizer.createSpeechRecognizer(applicationContext).apply { setRecognitionListener(object : RecognitionListener { override fun onReadyForSpeech(params: Bundle?) { android.util.Log.d(TAG, 语音识别准备就绪) } override fun onBeginningOfSpeech() { android.util.Log.d(TAG, 检测到语音开始) } override fun onRmsChanged(rmsdB: Float) { // 可用于显示音量波动动画 } override fun onBufferReceived(buffer: ByteArray?) {} override fun onEndOfSpeech() { android.util.Log.d(TAG, 检测到语音结束) } override fun onError(error: Int) { val errorMessage when (error) { SpeechRecognizer.ERROR_AUDIO - 音频错误 SpeechRecognizer.ERROR_CLIENT - 客户端错误 SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS - 权限不足 SpeechRecognizer.ERROR_NETWORK - 网络错误 SpeechRecognizer.ERROR_NETWORK_TIMEOUT - 网络超时 SpeechRecognizer.ERROR_NO_MATCH - 无匹配结果 SpeechRecognizer.ERROR_RECOGNIZER_BUSY - 识别器忙 SpeechRecognizer.ERROR_SERVER - 服务器错误 SpeechRecognizer.ERROR_SPEECH_TIMEOUT - 未检测到语音 else - 未知错误: $error } android.util.Log.w(TAG, 语音识别错误: $errorMessage) // 发生错误后尝试重新开始监听网络错误等可恢复错误 if (error ! SpeechRecognizer.ERROR_CLIENT) { restartListening() } } override fun onResults(results: Bundle?) { // 识别成功获取结果列表 val matches results?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION) val bestMatch matches?.get(0) // 取置信度最高的结果 bestMatch?.let { text - android.util.Log.i(TAG, 识别结果: $text) // 这里应该将识别到的文字发送到 UI 界面显示 // 例如通过 LiveData 或广播 sendRecognitionResultToUI(text) } // 一次识别结束后立即开始下一次监听实现连续识别 restartListening() } override fun onPartialResults(partialResults: Bundle?) { // 可以在这里处理部分结果实现实时字幕效果 } override fun onEvent(eventType: Int, params: Bundle?) {} }) } // 开始监听 restartListening() } private fun restartListening() { if (!isListening ::speechRecognizer.isInitialized) { val intent Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.CHINESE.language) // 设置识别语言为中文 putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true) // 启用部分结果 putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1) // 关键设置识别为持续模式非标准但通过连续触发实现 // 更优方案是使用 ACTION_RECOGNIZE_SPEECH 并处理 onEndOfSpeech 后重启 } try { speechRecognizer.startListening(intent) isListening true } catch (e: SecurityException) { android.util.Log.e(TAG, 无录音权限, e) } } } private fun stopSpeechRecognition() { if (isListening ::speechRecognizer.isInitialized) { speechRecognizer.stopListening() speechRecognizer.destroy() isListening false } } private fun sendRecognitionResultToUI(text: String) { // 通过 LocalBroadcastManager 或 ViewModel 将结果发送给 Activity val intent Intent(ACTION_RECOGNITION_RESULT).apply { putExtra(EXTRA_RECOGNIZED_TEXT, text) } LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent) } companion object { const val ACTION_RECOGNITION_RESULT com.example.assistivecall.ACTION_RECOGNITION_RESULT const val EXTRA_RECOGNIZED_TEXT recognized_text // ... 其他常量 ... } }4. 构建用户界面 (UI) 与交互UI 需要提供文本输入框、发送按钮、显示对方语音转文字结果的区域以及控制服务启动/停止的入口。4.1 主 Activity 布局与权限申请我们使用 Jetpack Compose 来构建一个简单的界面。首先在MainActivity中处理权限申请和服务绑定。// app/src/main/java/com/example/assistivecall/MainActivity.kt import android.Manifest import android.content.* import android.os.Build import android.os.Bundle import android.os.IBinder import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.example.assistivecall.ui.theme.AssistiveCallTheme class MainActivity : ComponentActivity() { private lateinit var callAssistService: CallAssistService private var isServiceBound false private val connection object : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { val binder service as CallAssistService.LocalBinder callAssistService binder.getService() isServiceBound true } override fun onServiceDisconnected(name: ComponentName?) { isServiceBound false } } // 权限请求合约 private val requestPermissionsLauncher registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissions - val allGranted permissions.all { it.value } if (allGranted) { // 权限全部授予启动服务 startAndBindService() } else { // 处理权限被拒绝的情况 // 可以提示用户或关闭应用 } } // 用于接收识别结果的广播接收器 private val recognitionReceiver object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action CallAssistService.ACTION_RECOGNITION_RESULT) { val text intent.getStringExtra(CallAssistService.EXTRA_RECOGNIZED_TEXT) text?.let { recognizedText - // 更新 UI 状态 // 这里需要通过 ViewModel 或其它方式更新 Compose 状态 } } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 注册广播接收器 LocalBroadcastManager.getInstance(this).registerReceiver( recognitionReceiver, IntentFilter(CallAssistService.ACTION_RECOGNITION_RESULT) ) // 检查并申请权限 checkAndRequestPermissions() setContent { AssistiveCallTheme { MainAppScreen() } } } private fun checkAndRequestPermissions() { val requiredPermissions mutableListOfString() requiredPermissions.add(Manifest.permission.RECORD_AUDIO) requiredPermissions.add(Manifest.permission.READ_PHONE_STATE) if (Build.VERSION.SDK_INT Build.VERSION_CODES.TIRAMISU) { requiredPermissions.add(Manifest.permission.POST_NOTIFICATIONS) } // 检查是否已授权 val permissionsToRequest requiredPermissions.filter { checkSelfPermission(it) ! PackageManager.PERMISSION_GRANTED }.toTypedArray() if (permissionsToRequest.isNotEmpty()) { requestPermissionsLauncher.launch(permissionsToRequest) } else { startAndBindService() } } private fun startAndBindService() { val serviceIntent Intent(this, CallAssistService::class.java) // 启动服务使其成为前台服务 if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { startForegroundService(serviceIntent) } else { startService(serviceIntent) } // 绑定服务以获取实例引用 bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE) } override fun onDestroy() { super.onDestroy() LocalBroadcastManager.getInstance(this).unregisterReceiver(recognitionReceiver) if (isServiceBound) { unbindService(connection) isServiceBound false } } } OptIn(ExperimentalMaterial3Api::class) Composable fun MainAppScreen() { var inputText by remember { mutableStateOf() } val recognizedMessages remember { mutableStateListOfString() } Surface(modifier Modifier.fillMaxSize()) { Column( modifier Modifier .fillMaxSize() .padding(16.dp), verticalArrangement Arrangement.SpaceBetween ) { // 区域1显示对方语音转换成的文字识别结果 Card( modifier Modifier .fillMaxWidth() .weight(1f), elevation CardDefaults.cardElevation(defaultElevation 4.dp) ) { LazyColumn(modifier Modifier.padding(8.dp)) { items(recognizedMessages) { message - Text( text 对方说$message, modifier Modifier.padding(vertical 4.dp) ) Divider() } } } Spacer(modifier Modifier.height(16.dp)) // 区域2文本输入和发送区域 Row( modifier Modifier.fillMaxWidth(), verticalAlignment Alignment.CenterVertically ) { OutlinedTextField( value inputText, onValueChange { inputText it }, label { Text(输入你想说的话) }, modifier Modifier.weight(1f), singleLine false ) Spacer(modifier Modifier.width(8.dp)) Button( onClick { // 调用服务的 speakText 方法 // 需要从 Activity 中获取 service 实例这里简化处理 // 实际应通过 ViewModel 或回调调用 if (inputText.isNotBlank()) { // callAssistService.speakText(inputText) inputText // 清空输入框 } }, enabled inputText.isNotBlank() ) { Text(发送) } } // 区域3快捷短语按钮 Text(快捷短语, modifier Modifier.padding(top 8.dp)) FlowRow( modifier Modifier.fillMaxWidth(), horizontalArrangement Arrangement.spacedBy(8.dp) ) { listOf(你好, 是的, 不是, 谢谢, 请稍等).forEach { phrase - FilterChip( selected false, onClick { // callAssistService.speakText(phrase) }, label { Text(phrase) } ) } } } } }4.2 在服务中实现 LocalBinder 供 Activity 调用为了让 Activity 能调用服务的speakText方法需要在服务中添加一个LocalBinder。// 在 CallAssistService 类中添加内部类 class CallAssistService : LifecycleService() { // ... 已有代码 ... private val binder LocalBinder() inner class LocalBinder : Binder() { fun getService(): CallAssistService thisCallAssistService } override fun onBind(intent: Intent): IBinder { super.onBind(intent) return binder } }5. 运行验证与关键测试点完成编码后需要在真实设备建议或支持电话功能的模拟器上进行测试。5.1 测试流程安装与授权安装 APK首次打开会弹出权限申请对话框务必全部允许。启动服务进入应用后前台服务应自动启动状态栏会出现常驻通知。模拟通话使用两部手机一部安装本应用作为发送方另一部作为接收方。使用模拟器Android Studio 的模拟器可以模拟来电和去电通过 Extended Controls 的 Phone 标签页。测试 TTS发送方 - 接收方在发送方应用输入框输入文字点击“发送”。接收方手机应能清晰地听到合成语音。测试 STT接收方 - 发送方让接收方对着手机说话。观察发送方应用界面是否能在“对方说”区域看到实时转换的文字。5.2 预期结果与日志观察服务启动Logcat 过滤CallAssistService应看到“TTS 初始化成功”。来电/去电当电话状态变化时应看到“Incoming call from: ...”或进入onCallConnected。TTS 播报点击发送后Logcat 应看到“TTS 初始化成功”如果已初始化和音频焦点请求的日志。接收方听到语音。STT 识别接收方说话时Logcat 应看到“检测到语音开始”、“检测到语音结束”以及“识别结果: ...”。发送方界面更新文字。6. 常见问题排查与优化在实际开发和测试中你可能会遇到以下问题。6.1 权限问题导致功能失效问题现象可能原因检查与解决方式应用启动后立刻崩溃或无法监听电话未动态申请READ_PHONE_STATE权限检查MainActivity中的权限申请逻辑确保在 Android 6.0 设备上弹窗并用户同意。点击发送无声音或 STT 不工作未授予RECORD_AUDIO权限同上确保录音权限已获取。在应用设置中检查权限状态。Android 12 通知不显示服务被杀死未申请POST_NOTIFICATIONS权限针对 API 33 设备必须动态申请此权限否则前台服务通知可能不显示导致服务被系统限制。Android 9 前台服务启动崩溃未声明FOREGROUND_SERVICE权限在AndroidManifest.xml中确认已添加此权限。6.2 音频相关问题问题现象可能原因检查与解决方式TTS 声音从手机扬声器而不是听筒发出TTS 未设置正确的音频流或音频焦点请求失败检查speakText方法中TextToSpeech.speak的参数params是否设置了KEY_PARAM_STREAM为STREAM_VOICE_CALL。确保requestAudioFocus成功。TTS 播报时对方听到回声或杂音音频管理冲突麦克风拾取了扬声器声音确保在 TTS 播报期间STT 监听是否暂停。可以在speakText开始时调用speechRecognizer.stopListening()播报完成后再restartListening()。STT 无法启动报ERROR_INSUFFICIENT_PERMISSIONS麦克风被其它应用占用或系统策略限制确保没有其他录音应用在运行。在通话中系统电话应用拥有最高优先级但我们的应用作为辅助工具通常可以共享。重启应用或手机。6.3 网络与识别问题问题现象可能原因检查与解决方式STT 一直返回ERROR_NETWORK_TIMEOUT或没有结果设备无网络连接或 Google 语音服务不可用检查设备网络。在中国大陆可能需要确认网络环境能访问 Google 服务。考虑添加离线识别备选方案。识别准确率低环境嘈杂、麦克风距离远、语言设置不匹配确保接收方在安静环境下清晰说话。检查RecognizerIntent.EXTRA_LANGUAGE是否设置为正确的语言区域如“zh-CN”。识别延迟高网络延迟或SpeechRecognizer配置问题考虑使用EXTRA_PARTIAL_RESULTS先显示中间结果提升体验。对于实时性要求极高的场景需评估离线引擎。6.4 服务与生命周期问题问题现象可能原因检查与解决方式退到后台后服务被系统杀死前台服务通知未正确设置或进程优先级低确保startForeground被成功调用且通知渠道已创建。检查是否在onCreate中启动了前台服务。考虑将服务设置为START_STICKY。电话挂断后STT 仍在监听onCallEnded中资源释放逻辑未执行检查PhoneStateListener的onCallStateChanged中CALL_STATE_IDLE分支是否被触发并正确调用了stopSpeechRecognition()。7. 生产环境最佳实践与扩展方向将上述 demo 转化为一个稳定、可用的产品还需要考虑更多方面。7.1 稳定性与健壮性增强服务保活与进程优先级将服务设置为START_STICKY并在onStartCommand中返回该标志使服务被系统杀死后能尝试重启。考虑使用JobScheduler或WorkManager在特定条件下如检测到服务不在运行且处于通话中重新启动服务。音频焦点精细管理实现AudioManager.OnAudioFocusChangeListener监听音频焦点变化。当焦点被其他应用如音乐播放器临时夺走时应暂停 TTS 播报并在焦点返回后恢复。在 TTS 播报前不仅请求焦点还应检查当前通话状态确保确实在通话中。错误处理与降级TTS 初始化失败时尝试切换语音引擎或提示用户安装语音数据。STT 网络不可用时提供“离线模式”允许用户预录制常用短语或使用更简单的输入方式如快捷短语矩阵。电池优化在非通话状态下停止 STT 的持续监听仅当通话接通后才启动通话结束后立即释放。使用WakeLock时要谨慎确保在不需要时及时释放。7.2 用户体验优化自定义 TTS 语音允许用户选择发音人、语速、音调使合成语音更自然。语音活动检测 (VAD)在 STT 持续监听中集成简单的 VAD 算法只在检测到人声时才触发识别减少误触发和资源消耗。实时字幕界面优化识别结果的显示支持滚动、字体大小调整、历史记录查看。快捷短语库允许用户自定义、分类管理常用语句支持一键发送。通话录音与回放在用户授权下录制完整通话双方语音并可将录音文件与文字转写关联供事后回顾。7.3 扩展方向离线语音识别集成集成如Mozilla DeepSpeech或Picovoice Cheetah等离线 STT 引擎实现完全离线的通话辅助解决网络依赖问题。多语言支持根据用户设置动态切换 TTS 和 STT 的语言。与系统拨号盘集成通过AccessibilityService或BroadcastReceiver监听系统拨号事件实现更无缝的启动体验。云端同步与个性化将用户的快捷短语、设置同步到云端实现多设备间同步。辅助功能 (Accessibility) 深度集成为视力障碍用户提供完整的 TalkBack 支持使其能独立操作整个应用。开发此类工具的核心在于深刻理解残障用户的实际场景和细腻需求技术实现上则要格外关注稳定性、实时性和隐私保护。每一个环节的可靠性都直接关系到工具能否在关键时刻真正帮到用户。