应用终止时无法接收Firebase Cloud Messaging推送通知求助
问题描述
已配置Firebase Cloud Messaging API(V1)服务端,实现点击通知图标按钮后5秒发送通知的功能:后台状态下可正常接收通知,但在5秒内关闭应用,应用终止状态下无法收到通知。需要解决应用终止状态下接收FCM推送的问题。
相关实现代码
服务端FCM工具类代码
import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:googleapis_auth/auth_io.dart'; final FirebaseFirestore _firestore = FirebaseFirestore.instance; final _credentials = ServiceAccountCredentials.fromJson({ "type": "service_account", "project_id": "proyecto-chatgpt-84b99", "private_key_id": "", "private_key": "-----BEGIN PRIVATE KEY-----" }); Future<String> getAccessToken() async { try { final client = http.Client(); final AccessCredentials credentials = await obtainAccessCredentialsViaServiceAccount(_credentials, ['https://www.googleapis.com/auth/firebase.messaging'], client); final accessToken = credentials.accessToken.data; client.close(); return accessToken; } catch (e) { print("Error obteniendo el token de acceso: $e"); throw e; } } Future<void> sendPushNotification( String token, String title, String body) async { try { const String sendUrl = 'https://fcm.googleapis.com/v1/projects/proyecto-chatgpt-84b99/messages:send'; final String accessToken = await getAccessToken(); final response = await http.post( Uri.parse(sendUrl), headers: { 'Authorization': 'Bearer $accessToken', 'Content-Type': 'application/json', }, body: json.encode({ 'message': { 'token': token, 'notification': { 'title': title, 'body': body, }, 'android': { 'priority': 'high', 'notification': { 'channelId': 'high_priority_channel', } }, }, }), ); if (response.statusCode != 200) { throw Exception('Failed to send notification: ${response.body}'); } } catch (e) { print("Error sending the notification: $e"); throw e; } } Future<void> sendPushNotificationToUser( String userId, String title, String body) async { try { // Obtiene el token FCM del usuario desde Firestore final docSnapshot = await _firestore.collection('users').doc(userId).get(); if (docSnapshot.exists && docSnapshot.data()?['fcmToken'] != null) { String userToken = docSnapshot.data()?['fcmToken']; await sendPushNotification(userToken, title, body); } else { print("Token FCM no encontrado para el usuario: $userId"); } } catch (e) { print("Error enviando la notificación: $e"); throw e; } }
主程序代码
import 'dart:ui'; import 'package:chatgpt_project/src/authentication/auth_google.dart'; import 'package:chatgpt_project/src/fcm/fcm_helper.dart'; import 'package:chatgpt_project/src/notifications/schedule_notifications.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:chatgpt_project/src/routes/routes.dart'; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:provider/provider.dart'; import 'package:chatgpt_project/src/providers/chats_provider.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/timezone.dart' as tz; import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter_native_timezone/flutter_native_timezone.dart'; final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); void main() async { WidgetsFlutterBinding.ensureInitialized(); tz.initializeTimeZones(); // Establecer la zona horaria local del dispositivo final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone(); tz.setLocalLocation(tz.getLocation(timeZoneName)); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); // 配置后台消息处理器 FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); await initializeDateFormatting('es_ES', null); // 配置通知渠道 const AndroidNotificationChannel channel = AndroidNotificationChannel( 'high_priority_channel', // id 'High Priority Notifications', // title description: 'Canal para notificaciones de alta prioridad', importance: Importance.high, playSound: true, showBadge: true, ); await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(channel); // 设置默认通知渠道 await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( alert: true, badge: true, sound: true, ); // Inicializa Firebase Messaging _configureFirebaseListeners(); final CallbackHandle callback = PluginUtilities.getCallbackHandle(callbackDispatcher)!; final int callbackHandle = callback.toRawHandle(); final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setInt('callback_handle', callbackHandle); runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => ChatProvider()), ], child: const MyApp(), ), ); } // 后台消息处理器 Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); // 显示本地通知 const AndroidNotificationDetails androidDetails = AndroidNotificationDetails( 'high_priority_channel', 'High Priority Notifications', channelDescription: 'Canal para notificaciones de alta prioridad', importance: Importance.high, priority: Priority.high, playSound: true, ); const NotificationDetails notificationDetails = NotificationDetails(android: androidDetails); await flutterLocalNotificationsPlugin.show( message.hashCode, message.notification?.title, message.notification?.body, notificationDetails, payload: message.data.toString(), ); } Future<void> updateTokenToFirestore(String token) async { // Obtiene el userId del usuario autenticado String? currentUserId = FirebaseAuth.instance.currentUser?.uid; if (currentUserId != null) { final docRef = _firestore.collection('users').doc(currentUserId); final docSnapshot = await docRef.get(); if (docSnapshot.exists) { await docRef.update({'fcmToken': token}); } else { await docRef.set({ 'fcmToken': token, }); } } } void _configureFirebaseListeners() async { final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance; // 请求通知权限 NotificationSettings settings = await _firebaseMessaging.requestPermission( alert: true, announcement: false, badge: true, carPlay: false, criticalAlert: false, provisional: false, sound: true, ); if (settings.authorizationStatus == AuthorizationStatus.authorized) { print('Usuario concedió permisos de notificación'); } // Obtener y manejar el token de FCM _firebaseMessaging.getToken().then((String? token) { assert(token != null); print("Token de FCM: $token"); updateTokenToFirestore(token!); }); // Escuchar cambios en el token _firebaseMessaging.onTokenRefresh.listen((String token) { print("Token de FCM actualizado: $token"); updateTokenToFirestore(token); }); _firebaseMessaging.getInitialMessage().then((RemoteMessage? message) { if (message != null) { // Manejar la notificación al abrir la app desde estado terminado print('Notificación recibida al abrir la app: ${message.notification?.title}'); } }); FirebaseMessaging.onMessage.listen((RemoteMessage message) { // Manejar notificación en primer plano const AndroidNotificationDetails androidDetails = AndroidNotificationDetails( 'high_priority_channel', 'High Priority Notifications', channelDescription: 'Canal para notificaciones de alta prioridad', importance: Importance.high, priority: Priority.high, playSound: true, ); const NotificationDetails notificationDetails = NotificationDetails(android: androidDetails); flutterLocalNotificationsPlugin.show( message.hashCode, message.notification?.title, message.notification?.body, notificationDetails, payload: message.data.toString(), ); }); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { // Manejar notificación al abrir la app desde segundo plano print('Notificación abierta desde segundo plano: ${message.notification?.title}'); }); } void fetchMedicinesFromBoot(List<String> args) async { String? userId = await getUserId(); if (userId != null) { fetchMedicines(userId); } } void callbackDispatcher() { const MethodChannel _backgroundChannel = MethodChannel('com.example.chatgpt_project/background_channel'); WidgetsFlutterBinding.ensureInitialized(); _backgroundChannel.setMethodCallHandler((MethodCall call) async { if (call.method == 'fetchMedicinesFromBoot') { fetchMedicinesFromBoot([]); } }); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'My App', debugShowCheckedModeBanner: false, initialRoute: Routes.loading, routes: Routes.routes, ); } }
测试按钮代码
child: IconButton( icon: const Icon(Icons.notifications, color: Colors.white, size: 33.0), onPressed: () async { try { // Obtiene el userId del usuario autenticado String? currentUserId = FirebaseAuth.instance.currentUser?.uid; if (currentUserId != null) { print('Preparando para enviar notificación...'); // Espera 5 segundos antes de enviar la notificación await Future.delayed(Duration(seconds: 5)); // Envia una notificación de prueba usando el userId await sendPushNotificationToUser( currentUserId, 'Notificación de prueba', 'Esta es una notificación de prueba'); print('Notificación enviada exitosamente.'); } else { print('No hay usuario autenticado.'); } } catch (e) { print("Error al intentar enviar notificación de prueba: $e"); } }, ),
AndroidManifest.xml配置
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.chatgpt_project"> <!-- Permiso para acceder a Internet --> <uses-permission android:name="android.permission.INTERNET" /> <!-- Permisos para las notificaciones --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <application android:label="chatgpt_project" 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> <meta-data android:name="flutterEmbedding" android:value="2" /> <!-- Receiver for handling boot completed to restore scheduled notifications after a reboot --> <receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver" android:exported="false"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"></action> <action android:name="android.intent.action.MY_PACKAGE_REPLACED"></action> </intent-filter> </receiver> <receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" android:exported="false" /> <!-- BootReceiver to handle re-scheduling notifications after device reboot --> <receiver android:name=".BootReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver> <!-- Configuración para el canal de notificaciones de Firebase --> <meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="high_priority_channel" /> </application> </manifest>
解决方案说明
要解决应用终止状态下无法接收FCM通知的问题,需要完成以下关键修改:
实现后台消息处理器
- 启用
FirebaseMessaging.onBackgroundMessage并实现对应的回调函数。应用终止状态下,FCM会触发这个后台回调,需在其中通过flutter_local_notifications插件显示本地通知。
- 启用
配置flutter_local_notifications插件
- 引入插件并创建高优先级通知渠道,确保通知能被系统正常显示;同时在前台消息监听逻辑中复用该插件,保持通知展示逻辑统一。
完善FCM消息Payload
- 服务端发送消息时,为Android平台指定与本地渠道一致的
channelId,确保通知关联到正确的高优先级渠道。
- 服务端发送消息时,为Android平台指定与本地渠道一致的
请求通知权限
- 在FCM初始化流程中主动请求用户的通知权限,避免因权限缺失导致通知无法展示。
设备端检查
- 确保设备未对应用启用电池优化,否则系统可能拦截后台消息推送。可引导用户在系统设置中关闭应用的电池优化选项。
内容的提问来源于stack exchange,提问作者newbie-flutter
相关产品推荐
相关产品推荐

