Flutter中Firebase离线推送通知联网后仅收一条的问题求助
解决Flutter Firebase离线推送通知只显示一条的问题
问题核心原因
Firebase Cloud Messaging(FCM)的通知消息(携带notification字段)在设备离线时,FCM服务器会自动合并同类型通知,仅保留最新一条;而数据消息(仅含data字段)不会被合并,离线时FCM会缓存所有消息,待设备联网后逐条推送给客户端,但这类消息需要你手动处理通知展示逻辑。
解决方案
要实现类似WhatsApp的全量离线通知接收效果,需采用纯Data消息推送,并在客户端手动处理所有场景下的通知展示:
1. 后端推送消息格式
发送仅含data字段的消息,将标题、内容、唯一标识等信息放入data中:
{ "to": "设备FCM_TOKEN", "data": { "title": "好友发来消息", "body": "今晚一起吃饭吗?", "messageId": "msg_20240520_001", // 每条消息的唯一ID,用于生成通知ID "customData": "自定义业务数据" } }
2. 客户端代码修改
修改你的Firebase通知处理逻辑,确保在前台、后台、应用杀死状态下都能正确展示每条离线推送的通知:
修改后的完整代码
import 'dart:convert'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; // 后台消息处理:纯Data消息在后台/杀死状态下会触发此方法 Future handleBackgroundMessage(RemoteMessage message) async { // 从data字段提取通知内容 final title = message.data['title'] ?? '默认标题'; final body = message.data['body'] ?? '默认内容'; final messageId = message.data['messageId'] ?? DateTime.now().millisecondsSinceEpoch.toString(); // 初始化本地通知插件(后台环境需重新初始化) final localNotification = FlutterLocalNotificationsPlugin(); const androidInit = AndroidInitializationSettings("@drawable/ic_launcher"); const iosInit = DarwinInitializationSettings(); await localNotification.initialize( InitializationSettings(android: androidInit, iOS: iosInit), ); // 创建通知渠道(仅Android需要) final androidChannel = const AndroidNotificationChannel( 'high_importance_channel', 'High Importance Notifications', description: "用于展示所有重要通知", importance: Importance.high, // 必须设为high才能在后台弹出通知 ); final androidPlugin = localNotification.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>(); await androidPlugin?.createNotificationChannel(androidChannel); // 显示本地通知 await localNotification.show( int.parse(messageId), // 用唯一messageId作为通知ID,避免覆盖 title, body, NotificationDetails( android: AndroidNotificationDetails( androidChannel.id, androidChannel.name, channelDescription: androidChannel.description, icon: "@drawable/ic_launcher", ), ), payload: jsonEncode(message.toMap()), ); } class FirebaseApi { final _firebaseMessaging = FirebaseMessaging.instance; final _androidChannel = const AndroidNotificationChannel( 'high_importance_channel', 'High Importance Notifications', description: "用于展示所有重要通知", importance: Importance.high, ); final _localNotification = FlutterLocalNotificationsPlugin(); void handleMessage(RemoteMessage? message) { if (message == null) return; // 处理通知点击后的逻辑 print("点击通知,数据:${message.data}"); } Future initLocalNotifications() async { const iOS = DarwinInitializationSettings(); const android = AndroidInitializationSettings("@drawable/ic_launcher"); const settings = InitializationSettings(android: android, iOS: iOS); await _localNotification.initialize(settings, onDidReceiveNotificationResponse: (response) { final message = RemoteMessage.fromMap(jsonDecode(response.payload!)); handleMessage(message); }); final platform = _localNotification.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>(); await platform?.createNotificationChannel(_androidChannel); } Future initPushNotifications() async { await FirebaseMessaging.instance .setForegroundNotificationPresentationOptions( alert: true, badge: true, sound: true); // 处理应用启动时的通知 FirebaseMessaging.instance.getInitialMessage().then(handleMessage); // 处理后台点击通知 FirebaseMessaging.onMessageOpenedApp.listen(handleMessage); // 注册后台消息处理 FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage); // 前台接收消息时展示通知 FirebaseMessaging.onMessage.listen( (message) { final title = message.data['title'] ?? '默认标题'; final body = message.data['body'] ?? '默认内容'; final messageId = message.data['messageId'] ?? DateTime.now().millisecondsSinceEpoch.toString(); _localNotification.show( int.parse(messageId), // 唯一ID确保每条通知独立显示 title, body, NotificationDetails( android: AndroidNotificationDetails( _androidChannel.id, _androidChannel.name, channelDescription: _androidChannel.description, icon: "@drawable/ic_launcher"), ), payload: jsonEncode(message.toMap()), ); }, ); } Future<String?> initNotification() async { await _firebaseMessaging.requestPermission( alert: true, badge: true, sound: true, provisional: false, ); String? fcmToken = await _firebaseMessaging.getToken(); print("FCM Token: $fcmToken"); await initLocalNotifications(); await initPushNotifications(); return fcmToken; } }
关键修改点说明
- 通知ID唯一性:使用消息自带的
messageId(或时间戳)作为通知ID,避免多条通知互相覆盖 - 后台消息处理:在
handleBackgroundMessage中手动初始化本地通知并展示,确保应用杀死/后台状态下也能接收并显示离线推送 - 纯Data消息处理:所有消息都从
data字段提取标题和内容,不再依赖FCM的notification字段 - Android通知渠道优先级:将渠道
importance设为Importance.high,确保通知能在后台正常弹出
额外配置
- iOS:在
Info.plist中开启后台远程通知权限:<key>UIBackgroundModes</key> <array> <string>remote-notification</string> </array> - Android:确保
AndroidManifest.xml中已添加FCM相关配置,且通知渠道创建正确
内容的提问来源于stack exchange,提问作者Berlato Matteo
相关产品推荐
相关产品推荐

