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

iOS前台Flutter FirebaseMessaging通知重复问题求助

iOS前台Firebase通知重复显示的问题解决

问题概述

开发Flutter + FirebaseMessaging通知测试应用时,Android端完全正常;iOS后台接收通知正常,但应用前台时通知会重复显示两次:带图片的通知一条显示图片、一条无图。无论通过Firebase控制台还是自研后端调用接口,问题均复现。

核心原因

  1. 双重通知触发:iOS平台下,同时开启了setForegroundNotificationPresentationOptions(alert: true)(系统自动展示前台通知)和flutter_local_notifications手动展示通知,导致重复。
  2. 图片展示差异:系统自动展示的通知会经过NotificationService扩展处理加载图片,而手动调用的通知未处理图片逻辑,因此出现一条有图一条无图的情况。

修复步骤

1. 关闭iOS系统自动展示前台通知

在main.dart中,针对iOS平台禁用系统自动展示前台通知,保留Android平台的设置:

await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
  alert: Platform.isAndroid, // iOS关闭系统自动弹通知
  badge: true,
  sound: true,
);

2. 优化本地通知插件初始化逻辑

当前NotificationManager每次调用都重新初始化插件,既低效也可能引发问题,改为全局初始化一次:

  • 修改main.dart,将插件初始化移至main函数:
import 'dart:developer';
import 'dart:io';
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 'package:googlenotifpoc/notification_manager.dart';
import 'firebase_options.dart';

// 全局声明本地通知插件实例
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    name: "...",
    options: DefaultFirebaseOptions.currentPlatform,
  );
  await FirebaseMessaging.instance.setAutoInitEnabled(true);

  // 初始化本地通知插件(全局只做一次)
  const androidInit = AndroidInitializationSettings("ic_white");
  const iosInit = DarwinInitializationSettings();
  const initialSettings = InitializationSettings(android: androidInit, iOS: iosInit);
  await flutterLocalNotificationsPlugin.initialize(initialSettings);

  const AndroidNotificationChannel channel = AndroidNotificationChannel(
    'high_importance_channel',
    'High Importance Notifications',
    description: 'This channel is used for important notifications.',
    importance: Importance.max,
  );

  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(channel);

  const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
    'high_importance_channel',
    'High Importance Notifications',
    channelDescription: 'This channel is used for important notifications.',
    importance: Importance.max,
    priority: Priority.high,
    ticker: 'ticker',
    icon: "ic_white",
    playSound: true,
  );

  await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
    alert: Platform.isAndroid, // iOS关闭系统自动弹通知
    badge: true,
    sound: true,
  );

  FirebaseMessaging.onMessage.listen((event) => NotificationManager.showNotification(event, androidDetails, flutterLocalNotificationsPlugin));

  runApp(const MyApp());
}

// 以下MyApp、MyHomePage代码保持不变...
  • 修改NotificationManager类,接收全局插件实例,添加图片处理逻辑:
import 'dart:convert';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;

class NotificationManager {
  static Future<void> showNotification(
    RemoteMessage payload,
    AndroidNotificationDetails androidDetails,
    FlutterLocalNotificationsPlugin plugin,
  ) async {
    NotificationDetails platformChannelSpecifics;
    const iOSDetails = DarwinNotificationDetails();

    // 处理带图片的通知
    if (payload.notification?.android?.imageUrl != null || payload.notification?.apple?.imageUrl != null) {
      final String? imageUrl = payload.notification?.android?.imageUrl ?? payload.notification?.apple?.imageUrl;
      if (imageUrl != null) {
        try {
          final http.Response response = await http.get(Uri.parse(imageUrl));
          final base64Image = base64Encode(response.bodyBytes);
          final bigPictureStyle = BigPictureStyleInformation(
            ByteArrayAndroidBitmap.fromBase64String(base64Image),
            largeIcon: ByteArrayAndroidBitmap.fromBase64String(base64Image),
          );
          final androidWithImage = androidDetails.copyWith(styleInformation: bigPictureStyle);
          platformChannelSpecifics = NotificationDetails(android: androidWithImage, iOS: iOSDetails);
        } catch (e) {
          // 图片加载失败时使用默认配置
          platformChannelSpecifics = NotificationDetails(android: androidDetails, iOS: iOSDetails);
        }
      } else {
        platformChannelSpecifics = NotificationDetails(android: androidDetails, iOS: iOSDetails);
      }
    } else {
      platformChannelSpecifics = NotificationDetails(android: androidDetails, iOS: iOSDetails);
    }

    // 用messageId的哈希值作为通知ID,避免重复覆盖
    await plugin.show(
      payload.messageId?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
      payload.notification!.title,
      payload.notification!.body,
      platformChannelSpecifics,
    );
  }
}

注意:需要在pubspec.yaml中添加http依赖:

dependencies:
  # 其他依赖...
  http: ^1.1.0

3. 保留NotificationService扩展配置

当前NotificationService.m的配置是正确的,它负责处理iOS后台通知的图片加载,无需修改。

验证效果

修改后,iOS前台只会收到一次由本地通知插件展示的通知,带图片的通知能正确显示图片;后台通知仍由系统正常处理,无重复问题。


内容的提问来源于stack exchange,提问作者Tom

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 03:52:02