You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Android应用终止/关闭时FCM推送通知失效求助

Flutter Android应用FCM推送问题:终止/关闭状态下通知失效,需登录后才生效

我开发的Flutter Android应用使用Firebase数据库,通过FCM实现推送通知。核心问题是:应用处于终止或关闭状态时,通知无法正常接收,必须完成登录操作后通知才会生效。已查阅YouTube教程及官方文档,问题仍未解决,相关代码如下:

main.dart 代码

// main.dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'login_screen.dart';
import 'notification_service.dart';

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
final NotificationService notificationService = NotificationService();

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp(); // 确保后台中Firebase已初始化

  notificationService.showNotification(
    message.data['title'],
    message.data['body'],
  );
  print("Handling a background message: ${message.messageId}");
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  // 注册后台消息处理器
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  // 初始化本地通知
  initializeLocalNotifications();

  // 为通知进行登录操作
  await _signInForNotifications();

  // 获取FCM令牌并保存到Firebase
  await _initializeFCMToken();

  runApp(const FireGuard());
}

class FireGuard extends StatelessWidget {
  const FireGuard({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'FireGuard Fire Safety',
      theme: ThemeData(
        primarySwatch: Colors.red,
      ),
      home: const LoginScreen(),
    );
  }
}

Future<void> _signInForNotifications() async {
  try {
    await FirebaseAuth.instance.signInWithEmailAndPassword(
      email: "",
      password: "",
    );
    print("已登录以接收通知。");
  } catch (e) {
    print("登录失败,无法接收通知: $e");
  }
}

Future<void> _initializeFCMToken() async {
  // 请求iOS通知权限
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  await messaging.requestPermission();

  // 获取FCM令牌
  String? token = await messaging.getToken();
  if (token != null) {
    await _saveTokenToNotificationNode(token);
  }

  // 监听令牌刷新,更新保存的令牌
  FirebaseMessaging.instance.onTokenRefresh.listen(_saveTokenToNotificationNode);
}

Future<void> _saveTokenToNotificationNode(String token) async {
  final DatabaseReference notificationRef =
      FirebaseDatabase.instance.ref().child("notifications");

  // 将FCM令牌直接作为键保存到notifications节点
  await notificationRef.child(token).set({
    "FCMToken": token,
    "isActive": true, // 可选字段,标记令牌状态
  });

  print("令牌已保存到Firebase的notifications节点: $token");
}

void initializeLocalNotifications() async {
  const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');
  const InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid);

  await flutterLocalNotificationsPlugin.initialize(initializationSettings);
}

notification_service.dart 代码

// notification_service.dart

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:vibration/vibration.dart';
import 'main.dart'; // 导入flutterLocalNotificationsPlugin

class NotificationService {
  final FirebaseMessaging _messaging = FirebaseMessaging.instance;

  NotificationService() {
    _initializeFCM();
  }

  // 初始化FCM
  void _initializeFCM() async {
    // 请求通知权限并订阅主题
    NotificationSettings settings = await _messaging.requestPermission(
      alert: true,
      announcement: true,
      badge: true,
      carPlay: false,
      criticalAlert: true,
      provisional: false,
      sound: true,
    );
    print('用户授权状态: ${settings.authorizationStatus}');

    await _messaging.subscribeToTopic('fire_alerts');

    // FirebaseMessaging.onMessage.listen((RemoteMessage message) {
    //   if (message.notification != null) {
    //      showNotification(
    //       message.data['title'] ?? '通知',
    //       message.data['body'] ?? '您有一条新消息。',
    //     );
    //   }
    // });

        // 处理后台消息点击
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
        // _handleNotificationClick(message.data);
       showNotification(
          message.data['title'] ?? '通知',
          message.data['body'] ?? '您有一条新消息。',
        );
    });


  }

  // 显示通知的公共方法
  void showNotification(String title, String body, {bool vibrate = false}) async {
    const AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails(
      'your_channel_id',
      '您的频道名称',
      channelDescription: '您的频道描述',
      importance: Importance.max,
      priority: Priority.high,
      showWhen: true,
    );

    const NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics);

    await flutterLocalNotificationsPlugin.show(
      0,
      title,
      body,
      platformChannelSpecifics,
    );

    // 如果支持振动且开启振动选项,则触发振动
    if (vibrate && (await Vibration.hasVibrator() ?? false)) {
      Vibration.vibrate(duration: 500); // 振动500毫秒
    }
  }

  // 将FCM令牌保存到Firebase(可根据需要自定义)
  Future<void> _saveTokenToFirebase(String token) async {
    print("FCM令牌已保存: $token");
  }

  // 发送系统通知的公共方法
  void sendSystemNotification(String title, String body, {bool vibrate = true}) {
    showNotification(title, body, vibrate: vibrate);
  }
}

DashboardNotification 相关代码

import 'package:fireguard/notification_service.dart';
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_phone_direct_caller/flutter_phone_direct_caller.dart';

import 'package:vibration/vibration.dart';

class DashboardNotification extends StatefulWidget {
  final String username;

  const DashboardNotification({super.key, required this.username});

  @override
  _DashboardNotificationState createState() => _DashboardNotificationState();
}

class _DashboardNotificationState extends State<DashboardNotification> {
  String? macAddress;
  DateTime? loginTime;
  final DatabaseReference _clientAccountsRef =
      FirebaseDatabase.instance.ref().child("Client Accounts");
  final DatabaseReference _devicesRef =
      FirebaseDatabase.instance.ref().child("devices");
  final NotificationService _notificationService = NotificationService();
  List<Map<String, dynamic>> _fireNotifications = [];
  final Map<String, String> _lastNotifiedTimestamp = {};

  @override
  void initState() {
    super.initState();
    loginTime = DateTime.now();
    // _initializeFCM(); // 初始化FCM以接收通知
    _loadUserDevicesAndFireHistory();
    //initializeNotifications();
  }

  Future<void> _saveLastNotifiedTimestamps() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    List<String> timestamps = _lastNotifiedTimestamp.entries
        .map((entry) => "${entry.key}:${entry.value}")
        .toList();
    await prefs.setStringList('lastNotifiedTimestamps', timestamps);
  }

    void _sendSystemNotification(String macAddress, String fireTimestamp, String severityLevel) {
    final DateTime now = DateTime.now();
    final DateTime fireEventTime = DateFormat('MM/dd/yyyy hh:mm:ss a').parse(fireTimestamp);

    // 检查火灾事件是否发生在登录之后,避免旧事件触发通知
    if (fireEventTime.isAfter(loginTime!)) {
      final DateTime? lastNotified = _lastNotifiedTimestamp[macAddress] != null
          ? DateFormat('MM/dd/yyyy hh:mm:ss a').parse(_lastNotifiedTimestamp[macAddress]!)
          : null;

      // 设置冷却时间(5分钟)
      const int cooldownPeriodInSeconds = 300;

      if (lastNotified == null || now.difference(lastNotified).inSeconds > cooldownPeriodInSeconds) {
        // 使用NotificationService发送带振动的通知
        _notificationService.sendSystemNotification(
          '检测到火灾! - 严重等级: $severityLevel',
          '设备$macAddress于$fireTimestamp检测到火灾 ',
          vibrate: true,
        );

        // 更新设备的最后通知时间戳
        _lastNotifiedTimestamp[macAddress] = DateFormat('MM/dd/yyyy hh:mm:ss a').format(now);

        // 将最后通知时间戳保存到本地存储
        _saveLastNotifiedTimestamps();
      } else {
        print('通知已抑制,避免重复发送(冷却生效中)');
      }
    } else {
      print('通知已抑制,火灾事件发生在登录之前。');
    }
  }

问题排查与解决方案

1. 后台消息处理依赖登录状态

应用终止/关闭时,后台消息处理器不会执行main函数中的_signInForNotifications()登录逻辑,导致Firebase操作可能失败,无法触发通知。

修复:
在后台处理器中添加匿名登录(需确保Firebase规则允许匿名访问):

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  // 后台环境使用匿名登录,避免Auth状态缺失
  try {
    await FirebaseAuth.instance.signInAnonymously();
  } catch (e) {
    print("匿名登录失败: $e");
  }
  // 直接初始化本地通知,不依赖全局实例
  const AndroidInitializationSettings initSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');
  const InitializationSettings initSettings = InitializationSettings(android: initSettingsAndroid);
  FlutterLocalNotificationsPlugin localPlugin = FlutterLocalNotificationsPlugin();
  await localPlugin.initialize(initSettings);

  const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
    'your_channel_id',
    '您的频道名称',
    description: '您的频道描述',
    importance: Importance.max,
    priority: Priority.high,
  );
  await localPlugin.show(
    0,
    message.data['title'],
    message.data['body'],
    NotificationDetails(android: androidDetails),
  );
  print("处理后台消息: ${message.messageId}");
}

2. 令牌保存与登录解耦

当前令牌保存依赖登录状态,若登录失败则令牌无法同步到数据库,服务器无法推送。

修复:
修改_initializeFCMToken(),移除对登录的依赖,确保无论登录状态如何都能保存令牌:

Future<void> _initializeFCMToken() async {
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  await messaging.requestPermission();

  String? token = await messaging.getToken();
  if (token != null) {
    await _saveTokenToNotificationNode(token);
  }

  FirebaseMessaging.instance.onTokenRefresh.listen(_saveTokenToNotificationNode);
}

3. 补全AndroidManifest配置

确保AndroidManifest中添加FCM所需权限和服务:

<!-- 添加权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

<!-- 添加FCM服务 -->
<service
    android:name="com.google.firebase.messaging.FirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

4. 创建Android通知渠道

Android 8.0+需显式创建通知渠道,否则后台通知无法显示:

void initializeLocalNotifications() async {
  const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');
  const InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid);

  // 创建通知渠道
  AndroidNotificationChannel channel = const AndroidNotificationChannel(
    'your_channel_id',
    '您的频道名称',
    description: '您的频道描述',
    importance: Importance.max,
  );
  await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel(channel);

  await flutterLocalNotificationsPlugin.initialize(initializationSettings);
}

内容的提问来源于stack exchange,提问作者L. Dayrit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 09:05:56