Android应用后台时热词检测服务被抢占麦克风后无法重启如何解决
解决方案
1. 补充前台服务必要的声明与权限
Android 10及以上系统要求麦克风相关前台服务必须显式声明服务类型,否则会被系统判定为非法后台访问,无法获取麦克风权限。
- 在
AndroidManifest.xml中为服务添加前台服务类型声明:
<service android:name=".HotKeyWordDetectionService" android:foregroundServiceType="microphone" android:exported="false" />
- 补充对应权限声明:
<!-- 基础录音权限 --> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- 前台服务基础权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- Android 12+ 麦克风前台服务专属权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
- 将
onStartCommand的返回值从START_NOT_STICKY改为START_STICKY,保证服务被系统异常回收后可以自动重启,重新申请麦克风资源。
2. 添加音频焦点监听与资源释放逻辑
你当前的报错核心原因是麦克风被其他应用抢占时,没有彻底释放PocketSphinx占有的AudioRecord资源,重试时资源冲突导致创建录音实例失败。
需要在服务中注册音频焦点监听器,失去焦点时彻底释放识别器资源,重新拿到焦点后再初始化识别器:
private AudioManager audioManager; private AudioFocusRequest audioFocusRequest; @Override public void onCreate() { super.onCreate(); audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); // 配置音频焦点请求,适配Android 8.0+ audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) .setAudioAttributes(new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ASSISTANT) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .build()) .setOnAudioFocusChangeListener(focusChange -> { if (focusChange == AudioManager.AUDIOFOCUS_LOSS || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { // 失去音频焦点,彻底释放识别器资源 releaseRecognizer(); } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN && recognizer == null) { // 重新获得焦点,初始化识别器 init(); } }).build(); // 请求音频焦点 audioManager.requestAudioFocus(audioFocusRequest); } private void releaseRecognizer() { if (recognizer != null) { recognizer.cancel(); recognizer.stop(); recognizer.shutdown(); recognizer = null; } }
同时修改init方法,每次初始化前先释放旧资源,初始化失败时添加延迟重试逻辑:
public void init() { service.submit(() -> { try { // 先释放旧资源避免冲突 releaseRecognizer(); Assets assets = new Assets(HotKeyWordDetectionService.this); File assetDir = assets.syncAssets(); setupRecognizer(assetDir); Log.i("sphinx_test", "setup success: "); handler.sendEmptyMessage(0); } catch (IOException e) { Log.i("sphinx_test", "setup failed: "+"Failed to init recognizer " + e.getMessage()); e.printStackTrace(); // 初始化失败,延迟3秒重试,避免频繁申请导致系统拦截 handler.postDelayed(() -> init(), 3000); } }); }
3. 补充麦克风状态轮询兜底逻辑
如果部分第三方应用抢占麦克风时没有触发系统音频焦点回调,可以添加定时轮询逻辑,每隔3秒检查当前麦克风占用状态:
private final Runnable micCheckRunnable = new Runnable() { @Override public void run() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { List<AudioRecordingConfiguration> configs = audioManager.getActiveRecordingConfigurations(); // 没有其他应用录音,且当前识别器未初始化 if (configs.isEmpty() && recognizer == null) { init(); } } handler.postDelayed(this, 3000); } }; // 在服务onStartCommand中启动轮询 @Override public int onStartCommand(Intent intent, int flags, int startId) { startForeground(1, createNotification()); service = Executors.newFixedThreadPool(3); handler.post(micCheckRunnable); init(); return START_STICKY; } // 服务销毁时停止轮询 @Override public void onDestroy() { super.onDestroy(); handler.removeCallbacks(micCheckRunnable); releaseRecognizer(); audioManager.abandonAudioFocusRequest(audioFocusRequest); }
内容的提问来源于stack exchange,提问作者SSP
相关产品推荐
相关产品推荐

