如何在Flutter应用终止状态下通过FCM通知触发音频播放并支持APP内暂停?
问题分析与解决方案
核心问题说明
你的代码无法正常工作的原因主要有三点:
- 应用终止状态下,
onBackgroundMessage运行在Dart隔离环境中,普通AudioPlayer实例无法在这个受限环境里完成音频初始化与播放——该环境没有完整的Flutter引擎上下文支持多媒体操作。 - 若发送的是带
notification字段的FCM通知,应用终止时系统会直接显示系统通知,不会触发onBackgroundMessage;只有纯数据消息或**通知+数据且配置了click_action: FLUTTER_NOTIFICATION_CLICK(Android)**的消息,才会触发后台回调。 - 后台播放的音频实例没有全局持久化,应用打开后无法关联到后台的播放进程,自然无法实现暂停操作。
实现步骤
1. 调整FCM消息格式
发送FCM时必须使用纯数据消息,确保应用终止时能触发后台回调。示例消息结构:
{ "to": "你的FCM_TOKEN", "data": { "audio_path": "assets/audio/alert.mp3", "title": "触发音频播放" } }
2. 原生层处理后台音频播放(关键)
Flutter隔离环境无法可靠处理后台音频,必须通过原生层实现:
- Android:在
FirebaseMessagingService的onMessageReceived中启动前台服务播放音频,保证后台/终止状态下的播放权限。 - iOS:配置音频会话为后台模式,在
AppDelegate的didReceiveRemoteNotification中启动音频播放。
3. Flutter层全局管理音频实例
使用全局单例AudioPlayer,确保应用打开后能关联到后台播放进程:
import 'package:audioplayers/audioplayers.dart'; // 全局单例音频播放器 final AudioPlayer globalAudioPlayer = AudioPlayer(); class AudioManager { // 播放本地音频 static Future<void> playLocalAudio(String path) async { await globalAudioPlayer.play(DeviceFileSource(path)); } // 暂停音频 static Future<void> pauseAudio() async { await globalAudioPlayer.pause(); } }
4. 重构FCM初始化逻辑
通过平台通道调用原生层播放音频,同时监听应用打开事件同步状态:
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/services.dart'; // 平台通道,用于调用原生音频播放方法 const platform = MethodChannel('com.your.app/audio_channel'); Future<void> backgroundHandler(RemoteMessage message) async { try { // 通过通道通知原生层播放音频 await platform.invokeMethod('playBackgroundAudio', { 'audioPath': message.data['audio_path'], }); } on PlatformException catch (e) { print('音频播放失败: ${e.message}'); } } class FcmNotifications { static Future<void> initialize() async { NotificationSettings settings = await FirebaseMessaging.instance.requestPermission(); if (settings.authorizationStatus == AuthorizationStatus.authorized) { String? fcmToken = await FirebaseMessaging.instance.getToken(); print('FCM Token: $fcmToken'); } FirebaseMessaging.onBackgroundMessage(backgroundHandler); // 应用从通知打开时,同步播放状态 FirebaseMessaging.onMessageOpenedApp.listen((message) { // 可在此初始化全局播放器,准备接受暂停操作 }); } }
5. 添加暂停按钮UI
在应用首页添加按钮,调用全局音频管理器的暂停方法:
ElevatedButton( onPressed: () => AudioManager.pauseAudio(), child: const Text('暂停音频'), )
原生层关键配置示例
Android(AndroidManifest.xml)
<!-- 声明前台服务权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <!-- 自定义Firebase消息服务 --> <service android:name=".MyFirebaseMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>
MyFirebaseMessagingService.java
public class MyFirebaseMessagingService extends FirebaseMessagingService { @Override public void onMessageReceived(RemoteMessage remoteMessage) { String audioPath = remoteMessage.getData().get("audio_path"); // 启动前台音频播放服务 Intent serviceIntent = new Intent(this, AudioPlaybackService.class); serviceIntent.putExtra("audio_path", audioPath); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent); } else { startService(serviceIntent); } } }
iOS(Info.plist)
<!-- 开启后台音频与远程通知权限 --> <key>UIBackgroundModes</key> <array> <string>audio</string> <string>remote-notification</string> </array>
注意事项
- 必须申请后台音频播放权限:Android需声明前台服务权限,iOS需配置
UIBackgroundModes。 - 纯数据消息不会触发系统通知,若需要显示通知,需在原生层手动创建。
- 全局音频播放器需与原生层播放实例保持状态同步,避免出现控制失效的情况。
内容的提问来源于stack exchange,提问作者Asif
相关产品推荐
相关产品推荐

