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); } }
额外注意事项
- 确保你的
MyFirebaseMessagingService继承的是com.google.firebase.messaging.FirebaseMessagingService,Manifest配置和你当前的一致即可,无需修改。 - 通知渠道是Android 8.0及以上的强制要求,一定要添加,否则通知可能无法显示。
- 使用
senderId作为通知ID,可以让同一聊天对象的新通知覆盖旧通知,避免通知栏堆积过多同类型通知。
内容的提问来源于stack exchange,提问作者martinseal1987
相关产品推荐
相关产品推荐

