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

Android Studio如何让虚拟助手App常驻后台监听唤醒词

问题根因

你之前的实现无法后台运行核心是3个硬伤:

  • 所有唤醒检测、语音识别逻辑全绑定在MainActivity生命周期,onStop回调里主动写了停止Porcupine、销毁SpeechRecognizer的逻辑,退到后台Activity被系统回收后所有功能自然终止
  • 你之前用的普通Service优先级极低,安卓系统内存紧张时会优先杀掉无可见通知的后台服务,根本无法常驻
  • 没有适配安卓8.0以后的后台执行限制、13+通知权限、14+前台服务麦克风类型要求,也没有处理国产ROM的电池优化查杀逻辑,就算服务拉起也会被系统断权限、杀进程

可行实现方案

第一步:修改Manifest配置

先在AndroidManifest.xml里加齐需要的权限、声明前台服务:

<!-- 必要权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- 安卓14必须声明前台服务类型权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- 安卓13+必须申请通知权限才能发前台服务通知 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- 申请电池优化白名单权限,降低被系统查杀概率 -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

<application
    ...>
    <!-- 声明常驻唤醒前台服务 -->
    <service
        android:name=".VoiceWakeupService"
        android:foregroundServiceType="microphone"
        android:exported="false" />

    <activity
        android:name=".MainActivity"
        ...>
        ...
    </activity>
</application>

第二步:实现独立前台服务

把所有唤醒检测、语音识别、指令响应逻辑从Activity挪到独立的前台服务VoiceWakeupService中,完全脱离Activity生命周期:

package com.eylon.jarvis;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import java.util.ArrayList;
import java.util.Locale;
import ai.picovoice.porcupine.Porcupine;
import ai.picovoice.porcupine.PorcupineException;
import ai.picovoice.porcupine.PorcupineManager;
import ai.picovoice.porcupine.PorcupineManagerCallback;

public class VoiceWakeupService extends Service {
    private static final String CHANNEL_ID = "wakeup_service_channel";
    private static final int NOTIFICATION_ID = 1001;
    private static final String ACCESS_KEY = "Oc8ZOSkVtJHWKhVW3iGMedHDSCSXn6P4vQtrQBl8hNLXwLmxLhs2AA==";
    private PorcupineManager porcupineManager = null;
    private SpeechRecognizer speechRecognizer;
    private Intent speechRecognizerIntent;
    private AppState currentState = AppState.STOPPED;
    private final Handler mainHandler = new Handler(Looper.getMainLooper());

    private enum AppState {
        STOPPED,
        WAKEWORD,
        STT
    }

    private void showToast(String message) {
        mainHandler.post(() -> Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show());
    }

    private final PorcupineManagerCallback porcupineCallback = keywordIndex -> mainHandler.post(() -> {
        try {
            porcupineManager.stop();
        } catch (PorcupineException e) {
            showToast("停止唤醒检测失败");
            return;
        }
        speechRecognizer.startListening(speechRecognizerIntent);
        currentState = AppState.STT;
    });

    @Override
    public void onCreate() {
        super.onCreate();
        // 创建通知渠道,启动前台服务(安卓8.0+必须,否则服务会直接被系统杀掉)
        createNotificationChannel();
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("Jarvis 运行中")
                .setContentText("等待唤醒指令")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setPriority(NotificationCompat.PRIORITY_LOW)
                .build();
        startForeground(NOTIFICATION_ID, notification);

        // 初始化语音识别、唤醒引擎
        initSpeechRecognizer();
        initPorcupine();
    }

    private void initSpeechRecognizer() {
        if (!SpeechRecognizer.isRecognitionAvailable(this)) {
            showToast("当前设备不支持语音识别");
            stopSelf();
            return;
        }
        speechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "en-US");
        speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, "请说出指令");

        // 注意必须用ApplicationContext创建,不要绑定Activity
        speechRecognizer = SpeechRecognizer.createSpeechRecognizer(getApplicationContext());
        speechRecognizer.setRecognitionListener(new RecognitionListener() {
            @Override public void onReadyForSpeech(Bundle params) {}
            @Override public void onBeginningOfSpeech() {}
            @Override public void onRmsChanged(float rmsdB) {}
            @Override public void onBufferReceived(byte[] buffer) {}
            @Override public void onEndOfSpeech() {}

            @Override
            public void onError(int error) {
                // 识别出错后自动回到唤醒检测状态,不要终止服务
                switch (error) {
                    case SpeechRecognizer.ERROR_AUDIO:
                        showToast("录音出错");
                        break;
                    case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
                        showToast("缺少录音权限");
                        break;
                    case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
                    case SpeechRecognizer.ERROR_NETWORK:
                        showToast("网络异常");
                        break;
                }
                startWakeupDetect(1000);
            }

            @Override
            public void onResults(Bundle results) {
                ArrayList<String> data = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
                if (data != null && !data.isEmpty()) {
                    String transcript = data.get(0).toLowerCase(Locale.ROOT);
                    handleVoiceCommand(transcript);
                }
                startWakeupDetect(3000);
            }

            @Override public void onPartialResults(Bundle partialResults) {}
            @Override public void onEvent(int eventType, Bundle params) {}
        });
        startWakeupDetect(0);
    }

    private void initPorcupine() {
        try {
            porcupineManager = new PorcupineManager.Builder()
                    .setAccessKey(ACCESS_KEY)
                    .setKeyword(Porcupine.BuiltInKeyword.JARVIS)
                    .setSensitivity(0.7f)
                    .build(getApplicationContext(), porcupineCallback);
        } catch (PorcupineException e) {
            showToast("唤醒引擎初始化失败:" + e.getMessage());
            stopSelf();
        }
    }

    private void startWakeupDetect(int delayMills) {
        speechRecognizer.stopListening();
        currentState = AppState.WAKEWORD;
        mainHandler.postDelayed(() -> {
            if (currentState == AppState.WAKEWORD && porcupineManager != null) {
                try {
                    porcupineManager.start();
                } catch (PorcupineException e) {
                    showToast("启动唤醒检测失败");
                }
            }
        }, delayMills);
    }

    private void handleVoiceCommand(String transcript) {
        if (transcript.equals("good morning")) {
            playAudioResponse(R.raw.good_morning);
        } else if (transcript.equals("who is your creator")) {
            playAudioResponse(R.raw.creator);
        }
    }

    private void playAudioResponse(int audioRes) {
        MediaPlayer player = MediaPlayer.create(getApplicationContext(), audioRes);
        player.setOnCompletionListener(MediaPlayer::release);
        player.start();
    }

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    CHANNEL_ID,
                    "唤醒服务通知",
                    NotificationManager.IMPORTANCE_LOW
            );
            channel.setDescription("语音助手后台运行通知");
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 服务被系统意外杀掉后自动重启
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (porcupineManager != null) {
            try {
                porcupineManager.stop();
                porcupineManager.delete();
            } catch (PorcupineException e) {
                e.printStackTrace();
            }
        }
        if (speechRecognizer != null) {
            speechRecognizer.destroy();
        }
        mainHandler.removeCallbacksAndMessages(null);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

第三步:精简MainActivity逻辑

删掉原来MainActivity里所有和Porcupine、SpeechRecognizer相关的初始化、生命周期处理代码,只保留权限申请、启动/停止服务的逻辑:

  • 必须删掉原来onStop回调里停止检测、销毁识别实例的代码,退后台不要干预服务运行
  • 首次启动时依次申请录音、通知权限,安卓12+还要引导用户手动开启「后台使用麦克风」权限
  • 开关按钮只负责发送启动/停止服务的Intent:
// MainActivity按钮点击逻辑示例
public void process(View view) {
    if (button.isChecked()) {
        if (checkAllNecessaryPermissions()) {
            Intent serviceIntent = new Intent(this, VoiceWakeupService.class);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(serviceIntent);
            } else {
                startService(serviceIntent);
            }
            // 引导用户将App加入电池优化白名单
            requestBatteryOptimizeWhiteList();
        } else {
            requestAllNecessaryPermissions();
            button.setChecked(false);
        }
    } else {
        stopService(new Intent(this, VoiceWakeupService.class));
    }
}

注意事项(避坑)
  • 小米、华为、OPPO、vivo等国产定制ROM默认有严格的后台查杀机制,就算是前台服务也可能被杀死,必须引导用户手动在系统设置里给App开启「自启动允许」「后台活动无限制」权限,没有通用绕开方案
  • 安卓12及以上版本,App退到后台后默认无法使用麦克风,除了声明权限,部分机型需要用户手动在权限设置里打开「后台允许使用麦克风」开关,否则后台唤醒会失效
  • 服务里创建SpeechRecognizer、Porcupine实例时必须用getApplicationContext(),不能传Activity实例,否则会绑定Activity生命周期,Activity销毁后实例也会被回收
  • 不要在服务里持有Activity的强引用,如果需要把识别状态同步到UI,用本地广播、LiveData等组件解耦

内容的提问来源于stack exchange,提问作者AGJ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 10:21:27