Oreo系统后台接收通知出现重复通知问题排查与解决
问题原因分析
我帮你梳理下为啥会出现两条通知的核心问题:
这是FCM消息类型与后台处理逻辑的冲突导致的:
- FCM分两种消息类型:
通知消息(Notification Message)和数据消息(Data Message)。 - 当应用在后台时,如果收到的是包含
notification字段的消息,FCM系统服务会自动帮你展示一条通知;如果此时你的自定义渠道没被FCM识别到(或者创建时机有问题),系统就会自动生成默认渠道来显示这条通知。 - 同时,你的
PushMessageReceiver又接收到了这条消息的内容,手动调用NotificationHandler再显示了一条自定义渠道的通知,最终就出现了两条重复通知。
另外,你虽然配置了com.google.firebase.messaging.default_notification_channel_id,但如果FCM自动处理通知时,自定义渠道还未完成创建(哪怕概率很低),也会触发系统创建默认渠道。
解决方法
根据你的业务需求,分两种场景处理:
场景1:完全自定义控制所有通知(推荐)
这种方式能彻底避免重复通知,步骤如下:
调整FCM消息发送结构:只发送数据消息,也就是消息体里只有
data字段,不要包含notification字段。示例结构:{ "to": "你的设备token", "data": { "title": "促销通知", "body": "新品上市", "messageid": "123", // 其他自定义字段... } }这样不管应用在前台还是后台,消息都会传递到你的
PushMessageReceiver,由你完全控制通知显示,FCM不会自动弹出通知,自然不会有重复或默认渠道的问题。优化渠道创建逻辑:在现有创建渠道的方法里增加判断,避免重复创建,确保渠道在FCM需要时已存在:
@RequiresApi(api = Build.VERSION_CODES.O) public static void createChannels(Context context) { NotificationManager manager = getManager(context); // 先检查渠道是否已存在,避免重复创建 NotificationChannel existingChannel = manager.getNotificationChannel(ANDROID_CHANNEL_ID); if (existingChannel == null) { NotificationChannel androidChannel = new NotificationChannel(ANDROID_CHANNEL_ID, ANDROID_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH); androidChannel.enableLights(true); androidChannel.enableVibration(true); androidChannel.setLightColor(android.app.Notification.DEFAULT_LIGHTS); androidChannel.setLockscreenVisibility(android.app.Notification.VISIBILITY_PRIVATE); manager.createNotificationChannel(androidChannel); } }
场景2:必须发送包含notification字段的消息
如果业务需求必须发送通知+数据的组合消息,需要避免重复处理:
- 判断应用前后台状态:在
PushMessageReceiver的onReceive方法里,先判断应用是否在后台,如果是后台就不手动显示通知(因为FCM已经自动处理了):@Override public void onReceive(Context context, Intent intent) { abortBroadcast(); Logger.d(Logger.TAG, "notification received"); // 判断应用是否在后台,后台时直接返回,避免重复显示 if (!isAppInForeground(context)) { return; } if (intent != null) { NotificationHandler.handleNotification(context, formNotificationObject(intent)); } } // 工具方法:判断应用是否处于前台 private boolean isAppInForeground(Context context) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses(); if (appProcesses == null) { return false; } final String packageName = context.getPackageName(); for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { if (appProcess.processName.equals(packageName) && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { return true; } } return false; } - 确认渠道ID完全匹配:再次检查Manifest里的
default_notification_channel_id值和NotificationHandler里的ANDROID_CHANNEL_ID完全一致(你的代码里都是com.caps,这部分没问题),这样FCM自动显示的通知会使用你的自定义渠道,不会创建默认渠道。
额外建议:迁移到FirebaseMessagingService
现在FCM官方推荐使用FirebaseMessagingService接收消息,比BroadcastReceiver更可靠,也更容易区分前后台逻辑。你可以替换现有Receiver为Service:
- 创建Service类:
public class MyFirebaseMessagingService extends FirebaseMessagingService { @Override public void onMessageReceived(RemoteMessage remoteMessage) { // 处理消息,数据消息或前台时的通知消息都会走到这里 Notification notification = formNotificationObject(remoteMessage); NotificationHandler.handleNotification(this, notification); } private Notification formNotificationObject(RemoteMessage remoteMessage) { Notification notification = new Notification(); // 从remoteMessage.getData()获取自定义字段 notification.setMessageId(Integer.parseInt(remoteMessage.getData().get("messageid"))); notification.setTitle(remoteMessage.getData().get("title")); notification.setBody(remoteMessage.getData().get("body")); // 其他字段处理... return notification; } } - 在Manifest里注册Service:
<service android:name=".MyFirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>
内容的提问来源于stack exchange,提问作者andro-girl
相关产品推荐
相关产品推荐

