Flutter应用冷启动无法读取Firebase通知数据 后台状态可正常读取
Flutter 冷启动点击Firebase通知无法读取数据跳转的解决方案
问题根因
FirebaseMessaging.onMessageOpenedApp流仅会在应用处于后台存活状态、点击通知唤起时触发,完全终止的冷启动场景不会走这个回调,这是现有代码漏处理的核心场景- 现有代码存在异步时序问题:
FirebaseNotification.init()调用后,没有等待冷启动场景的初始通知获取完成就直接启动App,同时全局导航key对应的Navigator还没完成挂载,就算拿到数据也会因为currentState为空报错 - 现有payload解析逻辑用字符串替换分割的写法非常脆弱,通知字段里如果带冒号、逗号就会解析失败
具体修改方案
1. 补充冷启动场景初始通知获取逻辑
首先修改firebase_notification.dart,调整init方法返回值为Future,新增冷启动初始消息的获取逻辑,同时把拿到的初始消息存为全局可访问变量,避免App没加载完就执行跳转报错:
// 新增全局变量存储冷启动拿到的通知数据 RemoteMessage? initialColdStartMessage; // 方法签名改成Future,支持初始化时await Future<void> FirebaseNotification.init() async { initNotification = InitNotification(); listenWhenMessageComes(); listenWhenAppInBackground(); listenWhenAppOpned(); listenWhenUserIsOnApp(); // 新增:获取冷启动场景的初始通知 await _getColdStartInitialMessage(); } // 新增:冷启动初始消息获取方法 Future<void> _getColdStartInitialMessage() async { // 该方法专门获取完全终止状态下点击通知唤起App时携带的消息 initialColdStartMessage = await FirebaseMessaging.instance.getInitialMessage(); } // 原有方法保留,处理后台存活场景的通知点击 Future<void> listenWhenAppOpned() async { FirebaseMessaging.onMessageOpenedApp.listen((event) async { if (event.notification != null) { // 直接传原生返回的data Map,不要转字符串再自行解析 await initNotification.onNotificationSelected(event.data); } }); }
2. 修复payload解析逻辑
修改init_notification.dart里的onNotificationSelected方法,直接接收Map类型参数,去掉脆弱的字符串分割逻辑,同时保证导航挂载完成后再执行跳转:
// 入参从String?改为直接接收Map<String, dynamic>类型的通知data Future<void> onNotificationSelected(Map<String, dynamic>? payLoad) async { print(payLoad); if (payLoad != null) { print("result:$payLoad"); // 等当前帧渲染完成、Navigator挂载完毕再执行跳转,避免空指针 WidgetsBinding.instance.addPostFrameCallback((_) async { await goToRespectedPage(payLoad); }); } }
3. 调整main.dart启动时序,等App挂载完成后处理冷启动通知
修改main.dart的逻辑,保证通知初始化完成后再启动App,等第一帧渲染完成后检查冷启动通知数据,有则执行跳转:
void main() async { HttpOverrides.global = new MyHttpOverrides(); WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); FirebaseDynamicLink.init(); // 等待通知初始化完成,拿到冷启动初始消息 await FirebaseNotification.init(); const MethodChannel('flavor') .invokeMethod<String>('getFlavor') .then((String? flavor) { print('STARTED WITH FLAVOR $flavor'); if (flavor == 'PROD') { startPROD(); } else if (flavor == 'UAT') { startUAT(); } }).catchError((error) { print(error); print('FAILED TO LOAD FLAVOR'); }); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); runApp(MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override void initState() { super.initState(); // 第一帧渲染完成后检查冷启动通知 WidgetsBinding.instance.addPostFrameCallback((_) { if (initialColdStartMessage != null) { FirebaseNotification.initNotification.onNotificationSelected(initialColdStartMessage!.data); // 处理完清空数据,避免重复跳转 initialColdStartMessage = null; } }); } @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, navigatorKey: AuthNav.navigationKey, onGenerateRoute: (RouteSettings settings) { return makeRoute( context: context, routeName: settings.name!, arguments: settings.arguments, ); }, ); } }
4. 优化跳转逻辑增加空判断
修改goToRespectedPage方法,增加导航状态空判断,避免极端场景下的空指针报错:
Future<void> goToRespectedPage(Map<String, dynamic> result) async { try { final navigatorState = AuthNav.navigationKey.currentState; // 导航未挂载时直接返回,不执行跳转 if (navigatorState == null) return; var routeName = NotificationRouteModel.fromJson(result).route; if (routeName.isNotEmpty) { if (userData != null) { switch (routeName) { case '/ViewPostPage': await navigatorState.pushReplacementNamed(routeName, arguments: int.parse(result['kmdpWallId'])); break; default: await navigatorState.pushNamed("/MainPage"); } } else { await navigatorState.pushNamed("/LoginPage"); } } } catch (err, stack) { print(err); print(stack); } }
验证注意点
- 冷启动测试时要先把应用从最近任务列表完全划掉,再点击通知栏通知拉起
- 通知的data字段不要放嵌套结构,保证key-value都是基础类型,避免解析异常
- Android12及以上版本要确认通知权限已经申请,否则通知可能无法正常展示
内容的提问来源于stack exchange,提问作者Developers If Else Solutions
相关产品推荐
相关产品推荐

