Flutter开发:实现后台/杀死状态下常驻Heads Up通知
Flutter实现后台/杀死状态下的常驻Heads Up通知(类似Uber接单通知)
要实现像Uber司机端那样的后台/杀死状态下触发Heads Up通知、持续播放提示音、常驻屏幕直至用户操作的效果,需要针对Android和iOS分别做平台特定配置,结合FCM(Firebase Cloud Messaging)和本地通知插件来实现。
核心思路
- 后台/杀死状态下,依赖推送服务(FCM/APNs)的数据消息而非通知消息,这样能完全自定义通知行为
- Heads Up通知需要设置高优先级、全屏意图(Android)或强制弹窗权限(iOS)
- 持续提示音无法通过系统通知声音实现(默认仅播放一次),需配合前台服务(Android)或后台音频任务(iOS)循环播放
Android 实现步骤
1. 配置权限与依赖
在android/app/src/main/AndroidManifest.xml中添加必要权限:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
在pubspec.yaml中添加依赖:
dependencies: firebase_messaging: ^14.6.1 flutter_local_notifications: ^14.1.1 audioplayers: ^4.0.1 # 用于播放循环提示音
2. 创建高优先级通知渠道
必须创建IMPORTANCE_HIGH级别的渠道,才能触发Heads Up:
final AndroidNotificationChannel _channel = AndroidNotificationChannel( 'urgent_notification_channel', '紧急通知渠道', description: '用于接单类高优先级通知', importance: Importance.high, sound: RawResourceAndroidNotificationSound('order_alert'), // 自定义提示音,放在res/raw下 enableVibration: true, vibrationPattern: Int64List.fromList([0, 1000, 500, 1000]), ); // 初始化时创建渠道 await FlutterLocalNotificationsPlugin().resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.createNotificationChannel(_channel);
3. 处理FCM后台消息
杀死/后台状态下,只有data类型的FCM消息会触发onBackgroundMessage回调,在这里构建常驻Heads Up通知:
Future<void> _firebaseBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp(); final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); // 初始化本地通知 await flutterLocalNotificationsPlugin.initialize( InitializationSettings( android: AndroidInitializationSettings('@mipmap/ic_launcher'), ), ); // 显示常驻Heads Up通知 await flutterLocalNotificationsPlugin.show( int.parse(message.data['notification_id']), message.data['title'], message.data['body'], NotificationDetails( android: AndroidNotificationDetails( _channel.id, _channel.name, channelDescription: _channel.description, importance: Importance.high, priority: Priority.high, visibility: NotificationVisibility.public, fullScreenIntent: true, // 强制弹出全屏Heads Up ongoing: true, // 常驻通知,无法滑动取消 autoCancel: false, // 手动操作后才取消 sound: RawResourceAndroidNotificationSound('order_alert'), ), ), payload: json.encode(message.data), ); // 启动循环提示音 _startLoopSound(); } // 循环播放提示音 void _startLoopSound() async { final player = AudioPlayer(); await player.setAsset('assets/sounds/order_alert.wav'); await player.setLoopMode(LoopMode.one); await player.play(); // 需保存player实例,在用户操作时停止 }
4. 处理通知操作
当用户点击或取消通知时,停止提示音并取消通知:
flutterLocalNotificationsPlugin.initialize( InitializationSettings(...), onDidReceiveNotificationResponse: (response) async { // 停止声音 await player.stop(); // 取消常驻通知 await flutterLocalNotificationsPlugin.cancel(int.parse(response.payload!['notification_id'])); }, );
iOS 实现步骤
1. 配置权限与后台模式
在Info.plist中添加:
<key>UIBackgroundModes</key> <array> <string>remote-notification</string> <string>audio</string> <!-- 用于后台播放声音 --> </array> <key>NSRemoteNotificationUsageDescription</key> <string>接收订单通知</string> <key>UNAuthorizationOptions</key> <array> <string>alert</string> <string>sound</string> <string>badge</string> </array>
2. 配置APNs与通知处理
在AppDelegate.swift中注册通知并设置代理:
import UserNotifications import Firebase @UIApplicationMain class AppDelegate: FlutterAppDelegate, UNUserNotificationCenterDelegate { override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { FirebaseApp.configure() // 请求通知权限 UNUserNotificationCenter.current().delegate = self let authOptions: UNAuthorizationOptions = [.alert, .sound, .badge] UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { _, _ in } application.registerForRemoteNotifications() GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } // 后台/杀死状态下接收通知时触发 override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // 构建自定义Heads Up通知 let content = UNMutableNotificationContent() content.title = userInfo["title"] as? String ?? "" content.body = userInfo["body"] as? String ?? "" content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "order_alert.wav")) content.badge = 1 content.categoryIdentifier = "urgent_order" let request = UNNotificationRequest(identifier: userInfo["notification_id"] as? String ?? "", content: content, trigger: nil) UNUserNotificationCenter.current().add(request) // 启动后台音频播放 startLoopSound() completionHandler(.newData) } // 前台显示Heads Up func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.alert, .sound, .badge]) } // 用户操作通知时停止声音 func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { stopLoopSound() completionHandler() } // 音频播放相关方法 var audioPlayer: AVAudioPlayer? func startLoopSound() { guard let url = Bundle.main.url(forResource: "order_alert", withExtension: "wav") else { return } do { audioPlayer = try AVAudioPlayer(contentsOf: url) audioPlayer?.numberOfLoops = -1 // 无限循环 audioPlayer?.play() } catch { print("播放失败: \(error)") } } func stopLoopSound() { audioPlayer?.stop() audioPlayer = nil } }
3. 注意事项
- iOS的通知声音最长支持30秒,若需要更长时间循环,需依赖后台音频任务
- 杀死状态下,只有当应用被APNs唤醒时才能执行代码,需确保推送消息包含
content-available: 1字段
关键注意点
- 推送消息必须使用data payload(Android)或包含
content-available: 1(iOS),才能在杀死状态下触发自定义逻辑 - Android的
fullScreenIntent需要API 26+,低版本可使用priority: Priority.max替代 - 自定义提示音需放在对应平台的资源目录(Android: res/raw;iOS: 主Bundle)
内容的提问来源于stack exchange,提问作者Rohit Jaiswal
相关产品推荐
相关产品推荐

