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

Firebase Cloud Messaging双Payload致重复通知问题求助

问题分析

你的核心问题是同时发送notification和data payload时,后台状态下系统会自动展示notification对应的通知,而你的onMessageReceived又处理data payload发送了另一条通知,导致重复。前台时系统不会自动展示通知,只有你代码发送的一条,所以前台表现正常。

另外你还需要实现“当前在聊天页面时不推送通知”的逻辑,我们一步步来解决:


解决重复通知问题

有两种可靠的方案,推荐第一种,因为能完全掌控通知逻辑:

方案1:仅发送Data Payload(推荐)

删除Cloud Function中的notification字段,只保留data payload。这样无论App在前台还是后台,FCM都会把消息交给onMessageReceived处理,完全由你控制通知的发送,避免系统自动生成通知。

修改后的Cloud Function代码:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

exports.notifyNewMessage = functions.firestore
.document('users/{ID}/contacts/{contactID}/messages/{messageID}')
.onCreate((docSnapshot , context) => {
  const message = docSnapshot.data()
  const recipientId = message['recipientId']
  const senderId = message['sender']
  const senderName = message['senderName']

  return admin.firestore().doc('users/' + recipientId).get().then(userDoc =>{
    const registrationTokens = userDoc.get('registeredToken')
    const notificationBody = (message['data_type'] === "TEXT") ? message['message'] : "You received a new image message."
    
    // 只保留data payload,新增senderId用于后续聊天页面判断
    const payload = {
      data : {
        TITLE : senderName + " sent you a message.",
        BODY : notificationBody,
        USER_NAME : senderName,
        USER_NUMBER : message['sender'],
        CLICK_ACTION : "MessageListActivity",
        MESSAGE : message['message'],
        SENDER_ID: senderId
      }
    }

    return admin.messaging().sendToDevice(registrationTokens,payload).then(response => {
      // 处理无效token,优化后续发送效率
      const tokensToRemove = [];
      response.results.forEach((result, index) => {
        const error = result.error;
        if (error) {
          console.error('Failure sending to token:', registrationTokens[index]);
          if (error.code === 'messaging/invalid-registration-token' ||
              error.code === 'messaging/registration-token-not-registered') {
            tokensToRemove.push(userDoc.ref.update({
              registeredToken: admin.firestore.FieldValue.arrayRemove(registrationTokens[index])
            }));
          }
        }
      });
      return Promise.all(tokensToRemove);
    })
  })
})

方案2:保留双Payload,但避免重复发送

如果你一定要保留notification payload,需要在MyFirebaseMessagingService中判断App是否处于后台,后台时不调用sendNotification(因为系统已经自动展示了notification的通知)。

先添加判断App前台状态的工具方法:

private boolean isAppInForeground() {
    ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
    if (appProcesses == null) {
        return false;
    }
    final String packageName = getPackageName();
    for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
        if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName.equals(packageName)) {
            return true;
        }
    }
    return false;
}

然后修改onMessageReceived:

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    // 检查data payload
    if (remoteMessage.getData().size() > 0) {
        Map<String, String> data = remoteMessage.getData();
        // 只有前台时才发送通知,后台由系统处理
        if (isAppInForeground()) {
            sendNotification(data);
        }
    }

    // 通知payload交给系统处理,无需额外操作
    if (remoteMessage.getNotification() != null) {
        // 空实现即可
    }
}

实现“聊天页面打开时不推送通知”

需要判断当前是否处于MessageListActivity,并且打开的聊天对象就是消息的发送者。

步骤1:在Activity中维护当前活跃的聊天ID

在MessageListActivity中,保存当前聊天的用户ID到全局变量:

public class MessageListActivity extends AppCompatActivity {
    public static String CURRENT_CHATTING_USER_ID = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 获取当前聊天的用户ID
        CURRENT_CHATTING_USER_ID = getIntent().getStringExtra(Constants.USER_NUMBER);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        CURRENT_CHATTING_USER_ID = null;
    }
}

步骤2:发送通知前判断

修改sendNotification方法,在发送前检查当前是否在聊天页面且对象匹配:

private void sendNotification(Map<String, String> data) {
    String senderId = data.get("SENDER_ID");
    // 判断是否正在和发送者聊天
    if (MessageListActivity.CURRENT_CHATTING_USER_ID != null && MessageListActivity.CURRENT_CHATTING_USER_ID.equals(senderId)) {
        // 正在聊天,不发送通知
        return;
    }

    // 原有的通知发送逻辑
    String author = data.get("USER_NAME");
    String title = data.get("TITLE");
    String body = data.get("BODY");
    String id = data.get("USER_NUMBER");
    String message = data.get("MESSAGE");

    Intent intent = new Intent(this, MessageListActivity.class);
    intent.putExtra(Constants.USER_NAME, author);
    intent.putExtra(Constants.USER_NUMBER, id);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);

    if (message.length() > NOTIFICATION_MAX_CHARACTERS) {
        message = message.substring(0, NOTIFICATION_MAX_CHARACTERS) + "\u2026";
    }

    // Android 8.0及以上需要创建通知渠道
    createNotificationChannel();

    Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
    NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "CHAT_NOTIFICATIONS")
            .setSmallIcon(R.drawable.ic_email_black_24dp)
            .setContentTitle(String.format(getString(R.string.notification_message), author))
            .setContentText(message)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setContentIntent(pendingIntent);

    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    // 使用senderId作为通知ID,避免同一聊天的通知堆叠
    int notificationId = Integer.parseInt(senderId);
    notificationManager.notify(notificationId, notificationBuilder.build());
}

// 创建通知渠道方法
private void createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        CharSequence name = getString(R.string.channel_name);
        String description = getString(R.string.channel_description);
        int importance = NotificationManager.IMPORTANCE_DEFAULT;
        NotificationChannel channel = new NotificationChannel("CHAT_NOTIFICATIONS", name, importance);
        channel.setDescription(description);
        NotificationManager notificationManager = getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);
    }
}

额外注意事项

  1. 确保你的MyFirebaseMessagingService继承的是com.google.firebase.messaging.FirebaseMessagingService,Manifest配置和你当前的一致即可,无需修改。
  2. 通知渠道是Android 8.0及以上的强制要求,一定要添加,否则通知可能无法显示。
  3. 使用senderId作为通知ID,可以让同一聊天对象的新通知覆盖旧通知,避免通知栏堆积过多同类型通知。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:51:39