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
相关产品推荐
相关产品推荐

