Flutter应用前台接收FCM通知时触发空指针异常求助
解决Flutter FCM前台通知NullPointerException问题
问题现象
应用处于后台或被杀死状态时,FCM通知可正常显示;但应用处于前台时,会抛出以下NullPointerException:
E/flutter (26732): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(error, Attempt to invoke virtual method 'int java.lang.Integer.intValue()' on a null object reference, null, java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.Integer.intValue()' on a null object reference E/flutter (26732): at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.setSmallIcon(FlutterLocalNotificationsPlugin.java:425) E/flutter (26732): at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.createNotification(FlutterLocalNotificationsPlugin.java:326) E/flutter (26732): at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.showNotification(FlutterLocalNotificationsPlugin.java:1146) E/flutter (26732): at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.show(FlutterLocalNotificationsPlugin.java:1462) E/flutter (26732): at com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin.onMethodCall(FlutterLocalNotificationsPlugin.java:1299) E/flutter (26732): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262) E/flutter (26732): at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295)
经调试,问题出在_configureForegroundMessaging()方法的通知显示代码中。
相关代码
主实现代码
late SharedPreferences _sharedPreferences; bool isLoggedIn = false; Future<void> _initializePrefs() async { log("Loading Shared Preferences"); _sharedPreferences = await SharedPreferences.getInstance(); log('Shared Preferences loaded'); isLoggedIn = _sharedPreferences.getBool(IS_LOGGED_IN) ?? false; } @pragma('vm:entry-point') Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(); await setupFlutterNotifications(); showFlutterNotification(message); print('Handling a background message ${message.messageId}'); } late AndroidNotificationChannel channel; bool isFlutterLocalNotificationsInitialized = false; Future<void> setupFlutterNotifications() async { if (isFlutterLocalNotificationsInitialized) { return; } channel = const AndroidNotificationChannel( 'high_importance_channel', // id 'High Importance Notifications', // title // 'This channel is used for important notifications.', // description importance: Importance.high, ); flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(channel); await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( alert: true, badge: true, sound: true, ); isFlutterLocalNotificationsInitialized = true; } void showFlutterNotification(RemoteMessage message) { RemoteNotification? notification = message.notification; AndroidNotification? android = message.notification?.android; if (notification != null && android != null && !kIsWeb) { flutterLocalNotificationsPlugin.show( notification.hashCode, notification.title, notification.body, NotificationDetails( android: AndroidNotificationDetails( channel.id, channel.name, icon: 'launch_background', ), ), ); } } void _configureForegroundMessaging() { FirebaseMessaging.onMessage.listen((RemoteMessage message) { log('new notificaiton: ${message.data}'); RemoteNotification? notification = message.notification; AndroidNotification? android = message.notification?.android!; if (notification != null && android != null) { log('Notification hascode: ${notification.hashCode} and notification title: ${notification.title} and notification body: ${notification.body}'); flutterLocalNotificationsPlugin.show( notification.hashCode, notification.title, notification.body, NotificationDetails( android: AndroidNotificationDetails( channel.id, channel.name, // channel.description, icon: 'launch_background', ), )); } }); } late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); if (!kIsWeb) { await setupFlutterNotifications(); } _configureForegroundMessaging(); SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); await _initializePrefs(); runApp(const MyApp()); }
Android Manifest配置
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.blog_app"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <application android:label="blog_app" android:name="${applicationName}" android:icon="@mipmap/ic_launcher"> <activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="com.google.firebase.messaging.FirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service> <meta-data android:name="flutterEmbedding" android:value="2" /> <meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="high_importance_channel" /> </application> </manifest>
解决方案
1. 修复通知图标问题
错误栈指向setSmallIcon方法,说明插件无法找到指定的图标资源。将icon: 'launch_background'替换为应用已存在的图标名称,比如启动图标ic_launcher(确保@mipmap/ic_launcher资源存在于Android项目中)。
2. 完善AndroidNotificationDetails参数
在AndroidNotificationDetails中添加channelDescription参数,部分插件版本要求该参数必填:
AndroidNotificationDetails( channel.id, channel.name, channelDescription: channel.description, // 补充描述 icon: 'ic_launcher', )
同时在setupFlutterNotifications中为channel添加description:
channel = const AndroidNotificationChannel( 'high_importance_channel', 'High Importance Notifications', description: 'This channel is used for important notifications.', importance: Importance.high, );
3. 修复空安全强制解包问题
_configureForegroundMessaging中AndroidNotification? android = message.notification?.android!;使用了强制解包!,若message.notification为null会直接崩溃,改为安全访问:
AndroidNotification? android = message.notification?.android;
4. 确保通知通道初始化完成
在显示通知前增加通道初始化状态检查,避免未初始化时调用:
if (notification != null && android != null && isFlutterLocalNotificationsInitialized) { // 显示通知逻辑 }
修改后的_configureForegroundMessaging方法
void _configureForegroundMessaging() { FirebaseMessaging.onMessage.listen((RemoteMessage message) { log('new notification: ${message.data}'); RemoteNotification? notification = message.notification; AndroidNotification? android = message.notification?.android; if (notification != null && android != null && isFlutterLocalNotificationsInitialized) { log('Notification hascode: ${notification.hashCode} and notification title: ${notification.title} and notification body: ${notification.body}'); flutterLocalNotificationsPlugin.show( notification.hashCode, notification.title, notification.body, NotificationDetails( android: AndroidNotificationDetails( channel.id, channel.name, channelDescription: channel.description, icon: 'ic_launcher', ), ), ); } }); }
原因分析
- 图标资源不存在:指定的
launch_background不是有效的drawable/mipmap资源,插件无法获取其资源ID,导致null值触发NPE。 - 空安全漏洞:强制解包操作
!会在对象为null时直接崩溃,违反空安全规范。 - 参数缺失:部分版本的flutter_local_notifications插件要求
AndroidNotificationDetails必须包含channelDescription,缺失会导致底层参数为空引发错误。
内容的提问来源于stack exchange,提问作者ayush
相关产品推荐
相关产品推荐

