Flutter自定义本地通知iOS端无声音问题排查求助
Flutter iOS端自定义通知声音不生效问题排查与解决
问题描述
- Android端使用本地通知包自定义通知声音正常,但iOS端接收通知时无法播放自定义声音。
- 用Postman向
https://fcm.googleapis.com/fcm/send发送以下请求时,iOS设备能正常播放指定的自定义声音:
{ "to" : "My device Id", "notification" : { "title" : "Sound test", "body" : "Sound body", "sound" : "sound.wav" } }
- 代码实现全局自定义声音时,通知能正常接收,但无自定义声音输出。经排查,问题出在
firebaseInit函数的Platform.isIOS分支——该分支仅调用了forgroundMessage方法,未执行iOS相关的本地通知初始化与展示逻辑。
相关代码
NotificationServices类完整代码
class NotificationServices { CustomerViewModel customerViewModel = CustomerViewModel(); FirebaseMessaging messaging = FirebaseMessaging.instance; final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); void initLocalNotifications( BuildContext context, RemoteMessage message) async { var androidInitializationSettings = const AndroidInitializationSettings('@drawable/ic_launcher'); var iosInitializationSettings = const DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true, ); var initializationSetting = InitializationSettings( android: androidInitializationSettings, iOS: iosInitializationSettings); await _flutterLocalNotificationsPlugin.initialize(initializationSetting, onDidReceiveNotificationResponse: (payload) { handleMessage(context, message); }); } void firebaseInit(BuildContext context) { FirebaseMessaging.onMessage.listen((message) { RemoteNotification? notification = message.notification; AndroidNotification? android = message.notification!.android; if (kDebugMode) { print("notifications title:${notification!.title}"); print("notifications body:${notification.body}"); print('count:${android!.count}'); print('data:${message.data.toString()}'); } if (Platform.isIOS) { forgroundMessage(); } if (Platform.isAndroid) { initLocalNotifications(context, message); showNotification(message); } }); } void requestNotificationPermission() async { NotificationSettings settings = await messaging.requestPermission( alert: true, announcement: true, badge: true, carPlay: true, criticalAlert: true, provisional: false, sound: true, ); if (settings.authorizationStatus == AuthorizationStatus.authorized) { if (kDebugMode) { print('user granted permission'); } } else if (settings.authorizationStatus == AuthorizationStatus.provisional) { if (kDebugMode) { print('user granted provisional permission'); } } else { if (kDebugMode) { print('user denied permission'); } } } Future<void> showNotification(RemoteMessage message) async { AndroidNotificationChannel channel = AndroidNotificationChannel( message.notification!.android!.channelId.toString(), message.notification!.android!.channelId.toString(), importance: Importance.max, showBadge: true, playSound: true, sound: const RawResourceAndroidNotificationSound('notification')); AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( 'bana_lazimsin_channel', 'High Importance Notifications', channelDescription: 'This channel is used for important notifications', importance: Importance.high, priority: Priority.high, playSound: true, ticker: 'ticker', icon: '@drawable/ic_launcher', color: const Color.fromARGB(255, 255, 15, 123), sound: channel.sound); DarwinNotificationDetails darwinNotificationDetails = const DarwinNotificationDetails(sound: 'notification.wav'); NotificationDetails notificationDetails = NotificationDetails( android: androidNotificationDetails, iOS: darwinNotificationDetails); Future.delayed(Duration.zero, () { _flutterLocalNotificationsPlugin.show( 0, message.notification!.title.toString(), message.notification!.body.toString(), notificationDetails, ); }); } Future<String> getDeviceToken() async { String? token = await messaging.getToken(); return token!; } void isTokenRefresh() async { messaging.onTokenRefresh.listen((event) { event.toString(); if (kDebugMode) { print('refresh'); } }); } Future<void> setupInteractMessage(BuildContext context) async { RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage(); if (initialMessage != null) { handleMessage(context, initialMessage); } FirebaseMessaging.onMessageOpenedApp.listen((event) { handleMessage(context, event); }); } void handleMessage(BuildContext context, RemoteMessage message) async { if (message.data['type'] == 'message') { String customerId = message.data['customerId']; Customer? customer = await customerViewModel.fetchOtherCustomer(customerId); Navigator.push( context, MaterialPageRoute( builder: (context) => ChatPage(customer: customer!))); } else if (message.data['type'] == 'Job') { String jobId = message.data['itemId']; int parsedJobId = int.parse(jobId); Navigator.push( context, MaterialPageRoute( builder: (context) => UserJobDetailPage( jobId: parsedJobId, ))); } else if (message.data['type'] == 'JobOffer') { String jobId = message.data['itemId']; int parsedJobId = int.parse(jobId); Navigator.push( context, MaterialPageRoute( builder: (context) => JobOfferDetailPage( jobId: parsedJobId, isActive: true, ))); } else if (message.data['type'] == 'NewJob') { Navigator.push( context, MaterialPageRoute( builder: (context) => const MainPage( isBidder: true, ), )); } else {} } Future forgroundMessage() async { await FirebaseMessaging.instance .setForegroundNotificationPresentationOptions( alert: true, badge: true, sound: true, ); } }
关键问题代码段
if (Platform.isIOS) { forgroundMessage(); }
该分支仅调用了设置前台通知展示选项的方法,未像Android端那样初始化本地通知并调用showNotification,导致自定义声音无法触发。
main.dart代码
@pragma('vm:entry-point') Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(); } void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle(statusBarColor: Colors.transparent)); runApp(const MyApp()); }
主页面initState调用代码
@override void initState() { loginViewModel = Provider.of<LoginViewModel>(context, listen: false); notificationServices.requestNotificationPermission(); notificationServices.forgroundMessage(); notificationServices.firebaseInit(context); notificationServices.setupInteractMessage(context); notificationServices.isTokenRefresh(); notificationServices.getDeviceToken().then((value) { if (kDebugMode) { print('device token'); print(value); } }); upBody = widget.isBidder ? const HomePageForBidder() : const HomePage(); for (var tab in tabIconsList) { tab.isSelected = false; } tabIconsList[0].isSelected = true; animationController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this); super.initState(); }
解决方案
修改firebaseInit函数,让iOS分支执行与Android端一致的本地通知初始化和展示逻辑:
void firebaseInit(BuildContext context) { FirebaseMessaging.onMessage.listen((message) { RemoteNotification? notification = message.notification; AndroidNotification? android = message.notification?.android; if (kDebugMode) { print("notifications title:${notification?.title}"); print("notifications body:${notification?.body}"); print('count:${android?.count}'); print('data:${message.data.toString()}'); } // 统一初始化本地通知并展示,覆盖iOS和Android initLocalNotifications(context, message); showNotification(message); // 保留iOS前台通知展示选项设置 if (Platform.isIOS) { forgroundMessage(); } }); }
额外检查项:
- 确认iOS项目的
Runner/Resources目录下存在notification.wav文件,且文件名大小写与代码中一致(iOS对文件名大小写敏感)。 - 若需后台通知也播放自定义声音,FCM请求的payload中必须包含
sound字段。 - 确保
DarwinNotificationDetails中指定的声音文件名与实际文件完全匹配。
内容的提问来源于stack exchange,提问作者Serhat Bilal
相关产品推荐
相关产品推荐

