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

如何解决聊天App登录时重复接收Firebase通知问题?

解决FCM通知重复接收的问题

问题根源分析

  1. 当前发送的是FCM的notification类型消息,这类消息会被FCM服务器缓存,当设备重新登录获取Token时,FCM会重新推送未被用户清除的缓存通知,导致重复接收。
  2. FirebaseNotificationService中重复调用了两次notify()方法,会导致同一条通知被触发两次。
  3. 没有对消息做去重校验,即使收到重复的消息请求,也会重复显示通知。

具体修复步骤

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 12:22:43