Flutter Firebase Messaging:应用终止时的数据消息处理问题
背景
我使用Flutter开发聊天应用,采用Firebase Cloud Messaging (FCM)发送数据消息(而非通知消息)。
服务器端发送逻辑
服务器发送消息的结构如下:
const message = { token: deviceToken, data: { senderName: senderName, message: messageContent, conversationId: conversationId, companyId: companyId, isGroup: 'false' } }
客户端已实现的处理逻辑
1. 后台消息处理器
Future<void> backgroundHandler(RemoteMessage message) async { // Handle background message LocalNotificationService.display(message); }
2. 应用初始化入口
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); FirebaseMessaging.onBackgroundMessage(backgroundHandler); runApp(MyApp()); }
3. 根组件初始化逻辑
void initState() { super.initState(); print("Inside Init"); // Initialize local notification service LocalNotificationService.initialize(); // Listen for messages when the app is in the foreground FirebaseMessaging.onMessage.listen((message) { print('Foreground message received: ${message.messageId}'); if (message.data.isNotEmpty) { print('Displaying Message'); LocalNotificationService.display(message); } }); // Check for initial message when the app is launched handleInitialMessage(); } Future<void> handleInitialMessage() async { RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage(); if (initialMessage != null) { Fluttertoast.showToast( msg: 'Inside Handle get initial message ${initialMessage.data}'); if (initialMessage.data.isNotEmpty) { handleRemoteMessage(initialMessage); } else if (initialMessage.notification != null) { Fluttertoast.showToast(msg: 'Notification caused app launch'); } else { Fluttertoast.showToast(msg: 'No data in initial message'); } } else { Fluttertoast.showToast(msg: 'No initial message received'); } } void handleRemoteMessage(RemoteMessage message) { // Extract and handle the message data Map<String, dynamic> data = message.data; final String companyId = data['companyId']; final String conversationId = data['conversationId']; final bool isGroup = data['isGroup'] == 'true'; User? currentUser = FirebaseAuth.instance.currentUser; if (currentUser != null) { String email = currentUser.email ?? ''; String employeeEmail = email.split('-')[1]; fetchEmployeeIdFromFirestore( incomingCompanyId: companyId, employeeEmail: employeeEmail, ).then((fetchedCompanyId) { if (fetchedCompanyId != null) { Navigator.pushNamed( navigatorKey.currentState!.context, '/ChatScreen', arguments: { 'companyId': companyId, 'employeeId': fetchedCompanyId, 'conversationId': conversationId, 'isGroup': isGroup, }, ); } else { print('Error: Employee email not found'); } }); } else { Navigator.pushNamed(navigatorKey.currentState!.context, '/LoginScreen'); } }
问题
应用处于**终止(已杀死)**状态时,用户点击通知后,message.data为空,无法触发目标页面导航。使用Firebase控制台发送测试通知时,同样得到空的{} Map。
解决方案
1. 修改本地通知展示逻辑,附加数据到payload
核心问题:后台处理数据消息时,本地通知未携带FCM的data字段,导致点击通知启动应用时无法获取原始数据。修改LocalNotificationService.display方法:
import 'dart:convert'; static Future<void> display(RemoteMessage message) async { var androidPlatformChannelSpecifics = AndroidNotificationDetails( 'your_channel_id', 'your_channel_name', importance: Importance.max, priority: Priority.high, ); var platformChannelSpecifics = NotificationDetails( android: androidPlatformChannelSpecifics, ); // 将FCM数据转为JSON字符串存入通知payload String payload = jsonEncode(message.data); await flutterLocalNotificationsPlugin.show( 0, message.data['senderName'], message.data['message'], platformChannelSpecifics, payload: payload, // 关键:绑定数据到通知 ); }
2. 监听本地通知点击事件,解析数据并导航
在LocalNotificationService.initialize()中添加点击监听,处理应用从终止状态启动的场景:
import 'dart:convert'; static Future<void> initialize() async { const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); final InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid); await flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: (String? payload) async { if (payload != null) { // 解析payload中的FCM数据 Map<String, dynamic> data = jsonDecode(payload); // 构建RemoteMessage对象或直接调用导航逻辑 RemoteMessage message = RemoteMessage(data: data); // 执行导航处理 handleRemoteMessage(message); } }); }
注意:handleRemoteMessage需保证全局可访问,或通过导航键确保上下文有效。
3. 确认Android配置正确性
在AndroidManifest.xml中确保Firebase消息服务配置正确:
<service android:name=".MyFirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>
若自定义了FirebaseMessagingService,需确保onMessageReceived方法正确转发数据消息到本地通知。
4. 验证服务器发送格式
确保服务器仅发送纯数据消息,不混合notification字段。FCM中,纯数据消息在后台/终止状态下会触发onBackgroundMessage;若同时包含notification字段,Android系统可能直接展示通知,跳过onBackgroundMessage处理。
关键原理
应用终止状态下,FCM数据消息会触发onBackgroundMessage,此时需通过本地通知将数据传递给用户。点击本地通知时,getInitialMessage()不一定能获取完整数据(纯数据消息场景下),因此必须通过本地通知的payload手动保存并传递数据,在点击时解析并执行导航逻辑。
内容的提问来源于stack exchange,提问作者Guru Subramani

