FCM通知点击跳转:Debug正常Prod环境APP关闭时页面闪退
问题解决:FCM通知点击跳转在生产环境应用关闭时页面短暂消失
问题根源
应用关闭状态下点击通知,FirebaseMessaging.instance.getInitialMessage()会触发跳转,但StartScreen的isConnected()方法里的Navigator.pushReplacement会在应用启动完成后执行——时序上通知跳转先执行,但随后被StartScreen的跳转覆盖,导致目标页面一闪而过。Debug模式下因为启动速度慢,时序刚好相反,所以没问题。
解决方案
核心思路:把通知的跳转意图先存起来,等StartScreen完成版本检查、登录状态判断的逻辑后,再执行通知跳转,避免被替换。
步骤1:新增全局变量存储通知跳转数据
在主文件顶部添加全局变量,用来保存从通知里拿到的跳转信息:
// 新增全局变量,存储通知跳转参数 Map<String, dynamic>? notificationRouteData; final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
步骤2:修改getInitialMessage的处理逻辑
不再直接调用handleScreen,而是把通知数据存到全局变量里:
FirebaseMessaging.instance.getInitialMessage().then((message) { if(message != null) { // 保存通知数据,不立即跳转 notificationRouteData = { "id": message.data["id"], "level": message.data["level"], "screen": message.data["screen"], "subject": message.data["subject"], "courseType": message.data["course_type"], }; } });
步骤3:修改StartScreen的isConnected方法
在版本检查和登录状态判断完成后,检查是否有未处理的通知跳转,执行跳转操作:
void isConnected(){ WidgetsBinding.instance.addPostFrameCallback((_)async{ String appRemoteVersion = await Utilities().remoteConfigValue("appVersion"); if(Constants.appLocalVersion != appRemoteVersion){ if(!mounted) return; Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => const AppNewVersionScreen() ) ); } else{ Widget targetScreen; if(LocalData().isConnected()){ targetScreen = const NavScreen(); } else{ targetScreen = const LoginScreen(); } // 先跳转到默认页面 if(!mounted) return; await Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => targetScreen) ); // 检查是否有通知跳转需要处理 if(notificationRouteData != null){ handleScreenFromNotification(notificationRouteData!); // 处理完清空数据,避免重复跳转 notificationRouteData = null; } } }); }
步骤4:新增handleScreenFromNotification方法
把原来的handleScreen逻辑改成这个方法,注意这里用push而不是pushReplacement,避免替换掉刚跳转到的默认页面:
void handleScreenFromNotification(Map<String, dynamic> data){ String screen = data["screen"] ?? ""; if(screen == "premium"){ navigatorKey.currentState?.push( MaterialPageRoute( builder: (context) => WillPopScope( onWillPop: () async { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => const NavScreen() ) ); return false; }, child: const PremiumScreen() ) ) ); } else if(screen == "aide"){ navigatorKey.currentState?.push( MaterialPageRoute( builder: (context) => WillPopScope( onWillPop: () async { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => const NavScreen() ) ); return false; }, child: const HelpScreen() ) ) ); } }
步骤5:保留其他FCM监听的原有逻辑
onMessage和onMessageOpenedApp的处理不需要修改,因为这两个场景下应用已经处于前台或后台,StartScreen的初始化逻辑已经完成,不会冲突:
FirebaseMessaging.onMessage.listen((RemoteMessage message) { handleScreen(message); }); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { if(message.notification != null) { handleScreen(message); } });
额外注意事项
- 确保
handleScreen方法里的跳转逻辑和handleScreenFromNotification一致,只是把pushReplacement改成push - 生产环境测试时,记得清理应用缓存后再测试,避免旧状态影响
- 如果有其他页面跳转逻辑,也要注意时序问题,避免类似的替换冲突
内容的提问来源于stack exchange,提问作者BigWatanga
相关产品推荐
相关产品推荐

