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

Flutter FCM自定义通知音效与后台/终止状态跳转问题求助

Flutter FCM 自定义音效+全状态跳转解决方案

核心思路

要同时满足自定义音效、无重复通知、全状态跳转,核心是禁止FCM自动创建通知,完全通过flutter_local_notifications手动处理所有通知逻辑,同时补全冷启动和后台点击的消息处理流程。


一、调整FCM推送Payload

移除顶层的notification对象,只保留data字段+平台专属音效配置,避免FCM自动弹通知导致的重复和默认音效问题。

正确Payload格式:

{  
  "message": {
    "token": "DEVICE_FCM_TOKEN",  
    "data": {  
      "title": "Visitor has been admitted!",  
      "body": "Dhaval developer (Visitor) has been admitted.",
      "id": "1215454",  
      "notification_type": "visitor_visited",  
      "other_data_key": "other_data_value"  
    },
    "android": {  
      "notification": {  
        "sound": "visitor_notification_sound"  
      }  
    },
    "apns":{ 
      "payload":{ 
          "aps":{ 
              "sound":"visitor_notification_sound.mp3",
              "badge": 1
          } 
      } 
    }
  }  
}

说明:

  • data字段必须包含title、body,用于手动创建通知内容
  • Android的sound值对应res/raw目录下的音效文件名(不带后缀)
  • iOS的sound值对应项目中添加的音效文件名(带后缀)

二、修改Flutter代码

1. 处理应用冷启动(终止状态)的通知点击

在main.dart中添加冷启动消息获取逻辑,确保应用从终止状态被通知唤醒时能处理跳转:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  
  // 处理冷启动时的通知消息
  RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
  if (initialMessage != null) {
    await FirebaseCloudMessagingService().handleNotificationTappedFormNotificationTray(initialMessage.data);
  }
  
  await FirebaseCloudMessagingService().initFCM();
  runApp(const MyApp());
}

2. 添加全局导航键用于跨状态跳转

在fcm_controller.dart顶部定义全局导航键,解决后台/冷启动时无法获取页面上下文的问题:

// 全局导航键,用于在任意状态下跳转页面
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

3. 完善后台通知点击逻辑

修改notificationTapBackground函数,添加实际跳转逻辑:

@pragma('vm:entry-point')
Future<void> notificationTapBackground(NotificationResponse notificationResponse) async {
  debugPrint('background notification tap');
  Map<String, dynamic> data = jsonDecode(notificationResponse.payload ?? "{}");
  // 通过全局导航键跳转至目标页面
  navigatorKey.currentState?.pushNamed('/visitor-detail', arguments: data);
}

4. 更新MyApp使用全局导航键

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: navigatorKey, // 绑定全局导航键
      routes: {
        '/': (context) => const Scaffold(body: Center(child: Text('hello'))),
        '/visitor-detail': (context) => const VisitorDetailPage(), // 定义目标页面
      },
    );
  }
}

5. 优化通知创建逻辑

确保showNotification完全从data字段读取内容,并正确创建通知渠道:

static Future<void> showNotification(RemoteMessage message) async {
  String title = message.data['title'] ?? '默认通知';
  String body = message.data['body'] ?? '无内容';
  String soundName = 'notification_sound_android';
  String iosSoundName = 'notification_sound_android.mp3';
  
  if (message.data['notification_type'] == 'visitor_visited') {
    soundName = 'visitor_notification_sound';
    iosSoundName = 'visitor_notification_sound.mp3';
  }
  
  AndroidNotificationChannel channel = AndroidNotificationChannel(
    soundName,
    '访客通知',
    importance: Importance.max,
    playSound: true,
    sound: RawResourceAndroidNotificationSound(soundName),
  );
  
  // Android 8.0+必须先创建通知渠道
  await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel(channel);
  
  AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
    channel.id,
    channel.name,
    sound: RawResourceAndroidNotificationSound(soundName),
  );
  
  NotificationDetails notificationDetails = NotificationDetails(
    android: androidDetails,
    iOS: DarwinNotificationDetails(sound: iosSoundName),
  );
  
  flutterLocalNotificationsPlugin.show(
    math.Random().nextInt(100000),
    title,
    body,
    notificationDetails,
    payload: jsonEncode(message.data),
  );
}

三、平台配置注意事项

Android

  1. 将自定义音效文件(如visitor_notification_sound.mp3)放在android/app/src/main/res/raw目录下(无raw目录则新建)
  2. 音效文件名必须全小写,无空格或特殊字符
  3. 无需在AndroidManifest.xml额外配置音效,代码中已通过通知渠道处理

iOS

  1. 将音效文件添加到Xcode项目,勾选"Copy items if needed"和对应target
  2. 在Info.plist中添加后台推送权限:
<key>UIBackgroundModes</key>
<array>
  <string>remote-notification</string>
</array>
  1. 确保音效格式为iOS支持的类型(如mp3、wav),时长建议不超过30秒

四、验证步骤

  1. 发送调整后的Payload到测试设备
  2. 测试三种状态下的表现:
    • 应用前台:收到自定义音效通知,点击跳转正常
    • 应用后台:收到自定义音效通知,点击跳转正常
    • 应用终止:收到自定义音效通知,点击唤醒应用并跳转正常
  3. 确认无重复通知,仅播放自定义音效

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 22:17:32