You need to enable JavaScript to run this app. Android收到Firebase通知时前台服务播放声音崩溃如何解决
优惠活动
大模型
产品
解决方案
定价
更多

Android收到Firebase通知时前台服务播放声音崩溃如何解决

崩溃核心原因
  • 触发本次直接崩溃的根因:在FirebaseMessaging.onMessageReceived里手动new ListenerService()创建服务实例。Android四大组件(Service属于其中之一)不是普通Java类,只有通过系统API启动时,系统才会为其初始化内部持有的Context基础环境。手动new出来的ListenerService对象是完全未初始化的空壳,内部的Context字段为null,调用playSound方法时,MediaPlayer.create()需要通过Context获取资源,直接触发NPE,和报错日志中“调用null对象的getResources方法”完全匹配。
  • 设计逻辑上的偏差:原本要实现“收到通知启动前台服务”的需求,现有代码完全没有走系统服务启动流程,就算不发生NPE,也执行不到startForeground的逻辑,达不到前台服务保活的效果;同时MediaPlayer创建后没有调用start方法、没有播放完成的资源释放逻辑,就算Context正常也不会实际播放提示音。
分步修复方案
  • 第一步:替换Firebase消息处理里的错误调用,删掉手动new服务的代码,改用系统正规的服务启动API。这里要注意Android 8.0之后对后台启动服务的限制,收到推送时应用大概率处于后台,必须用前台服务的启动方式:
public class FirebaseMessaging extends FirebaseMessagingService {
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);
        Intent serviceIntent = new Intent(this, ListenerService.class);
        // 适配Android 8.0+的后台启动限制
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(serviceIntent);
        } else {
            startService(serviceIntent);
        }
    }
}
  • 第二步:把播放提示音的逻辑挪到服务内部,在服务被系统正常创建、Context初始化完成后再执行播放,补全MediaPlayer的生命周期逻辑,同时适配新版本系统的前台服务启动规则:
public class ListenerService extends Service {
    private MediaPlayer mediaPlayer;
    // 提前定义好通知渠道ID
    private static final String CHANNEL_ID = "listener_service_channel";

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 构建前台服务通知
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(
                this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
        );
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.icon)
                .setContentText("FoneFinder")
                .setContentTitle("Keeping eye on your phone")
                .setContentIntent(pendingIntent)
                .build();
        // Android 12+启动前台服务必须指定前台服务类型
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
        } else {
            startForeground(1, notification);
        }
        // 服务正常启动后再执行播放逻辑,此时Context已经由系统初始化完成,不会出现空指针
        playSound();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 服务销毁时释放MediaPlayer资源,避免内存泄漏
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }

    private void playSound() {
        mediaPlayer = MediaPlayer.create(this, R.raw.a);
        if (mediaPlayer == null) return;
        mediaPlayer.setLooping(false);
        mediaPlayer.start();
        mediaPlayer.setOnCompletionListener(mp -> {
            mp.release();
            mediaPlayer = null;
        });
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
  • 第三步:补全Manifest里的必要声明,避免后续出现新的崩溃:
    • 加权限声明:<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />,适配Android 14版本增加<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
    • 给ListenerService组件加上对应属性:android:foregroundServiceType="mediaPlayback"
    • 提前创建好通知渠道:API 26以上的版本,应用启动时就要为前台服务的CHANNEL_ID创建对应通知渠道,不然startForeground会触发渠道不存在的崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.11 20:45:01