如何解决聊天App登录时重复接收Firebase通知问题?
解决FCM通知重复接收的问题
问题根源分析
- 当前发送的是FCM的notification类型消息,这类消息会被FCM服务器缓存,当设备重新登录获取Token时,FCM会重新推送未被用户清除的缓存通知,导致重复接收。
FirebaseNotificationService中重复调用了两次notify()方法,会导致同一条通知被触发两次。- 没有对消息做去重校验,即使收到重复的消息请求,也会重复显示通知。
具体修复步骤
1. 修改FCM消息发送逻辑,改用Data消息
将FCMSender中的notification消息改为data消息,FCM服务器不会缓存这类消息,完全由客户端控制处理逻辑:
public class FCMSender { Context context; private final String postUrl = "https://fcm.googleapis.com/fcm/send"; private final String fcmServerKey = "fcmServerKey"; public FCMSender(Context context) { this.context = context; } public void pushNotification(String token, String title, String body, String messageId) { // 添加唯一messageId参数 StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); RequestQueue requestQueue = Volley.newRequestQueue(context); JSONObject mainObj = new JSONObject(); try { mainObj.put("to", token); // 改用data字段传递消息内容 JSONObject dataObj = new JSONObject(); dataObj.put("title", title); dataObj.put("body", body); dataObj.put("messageId", messageId); // 传递唯一消息ID用于去重 dataObj.put("icon", R.drawable.logo); mainObj.put("data", dataObj); // 替换原来的notification字段为data JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, postUrl, mainObj, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { } }) { @Override public Map<String, String> getHeaders() throws AuthFailureError { Map<String, String> header = new HashMap<>(); header.put("Content-Type", "application/json"); header.put("Authorization", "key=" + fcmServerKey); return header; } }; requestQueue.add(request); } catch (JSONException e) { e.printStackTrace(); } } }
调用
pushNotification时,需要传入唯一的messageId(可通过UUID.randomUUID().toString()生成)。
2. 修改FirebaseNotificationService,添加去重逻辑并修复重复通知问题
@SuppressLint("MissingFirebaseInstanceTokenRefresh") public class FirebaseNotificationService extends FirebaseMessagingService { public static final String CHANNEL_ID_1 = "channel_1"; public static final String CHANNEL_ID_2 = "channel_2"; private static final String PREFS_NAME = "NotificationPrefs"; private static final String KEY_PROCESSED_MESSAGES = "processed_messages"; @SuppressLint("NewApi") @Override public void onMessageReceived(RemoteMessage remoteMessage) { // 从data字段获取消息内容 Map<String, String> data = remoteMessage.getData(); String title = data.get("title"); String text = data.get("body"); String messageId = data.get("messageId"); // 校验消息有效性 if (title == null || text == null || messageId == null) { super.onMessageReceived(remoteMessage); return; } // 检查是否已经处理过这条消息 SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); Set<String> processedMessages = prefs.getStringSet(KEY_PROCESSED_MESSAGES, new HashSet<>()); if (processedMessages.contains(messageId)) { // 已处理过,直接返回 super.onMessageReceived(remoteMessage); return; } // 标记消息为已处理 processedMessages.add(messageId); prefs.edit().putStringSet(KEY_PROCESSED_MESSAGES, processedMessages).apply(); // 处理通知逻辑 PendingIntent contentIntent = PendingIntent.getActivity( this, messageId.hashCode(), // 用messageId哈希值作为requestCode,避免PendingIntent冲突 new Intent(this, SplashActivity.class), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); // 适配Android 12+ String channelId; if (title.contains("chat")) { channelId = CHANNEL_ID_2; NotificationChannel channel_chat = new NotificationChannel( CHANNEL_ID_2, "Message Notification", NotificationManager.IMPORTANCE_HIGH); getSystemService(NotificationManager.class).createNotificationChannel(channel_chat); } else { channelId = CHANNEL_ID_1; NotificationChannel channel = new NotificationChannel( CHANNEL_ID_1, "Message Notification", NotificationManager.IMPORTANCE_HIGH); getSystemService(NotificationManager.class).createNotificationChannel(channel); } Notification.Builder notification = new Notification.Builder(this, channelId) .setContentTitle(title) .setContentText(text) .setSmallIcon(R.drawable.logo) .setContentIntent(contentIntent) .setPriority(Notification.PRIORITY_DEFAULT) .setAutoCancel(true); // 只调用一次notify,用messageId哈希值作为通知ID,确保不同消息独立显示 int notificationId = messageId.hashCode(); NotificationManagerCompat.from(this).notify(notificationId, notification.build()); broadcastNewNotification(); super.onMessageReceived(remoteMessage); } private void broadcastNewNotification() { Intent intent = new Intent(NEW_NOTI); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } public static final String NEW_NOTI = "new_notification"; }
3. 额外优化点
- 定期清理已处理的消息ID集合:可在App启动时删除超过7天的消息ID,避免SharedPreferences存储过大。
- 保持通知ID与消息ID绑定:确保不同聊天消息显示为独立通知,不会互相覆盖。
内容的提问来源于stack exchange,提问作者Hocine Djouamaa
相关产品推荐
相关产品推荐

