如何在iOS UNNotificationServiceExtension中调用Flutter代码
方案1:App Groups共享状态 + URL Scheme后台唤醒主App
由于UNNotificationServiceExtension无法直接初始化Flutter引擎,我们可以通过共享数据+唤醒主App的方式间接触发Flutter代码:
配置App Groups
在Xcode中为主App和通知扩展开启同一个App Group(项目配置 -> Signing & Capabilities -> 添加App Groups,填写统一的group ID,比如group.com.your.app)。扩展侧写入任务数据并唤醒主App
在UNNotificationServiceExtension的didReceive(_:withContentHandler:)方法中,将需要Flutter执行的任务信息写入App Group的UserDefaults,然后通过自定义URL Scheme唤醒主App:override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) // 写入任务数据到App Group let groupDefaults = UserDefaults(suiteName: "group.com.your.app") groupDefaults?.set("需要执行的任务标识/数据", forKey: "PendingFlutterTask") groupDefaults?.synchronize() // 通过URL Scheme唤醒主App if let url = URL(string: "your-app-scheme://trigger-flutter-task") { UIApplication.shared.open(url, options: [:], completionHandler: nil) } contentHandler(bestAttemptContent ?? request.content) }主App侧接收唤醒并触发Flutter代码
在主App的AppDelegate(或SceneDelegate)中处理URL Scheme跳转,读取App Group的数据后通过MethodChannel传递给Flutter:func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { guard url.scheme == "your-app-scheme", url.host == "trigger-flutter-task" else { return false } let groupDefaults = UserDefaults(suiteName: "group.com.your.app") guard let taskData = groupDefaults?.string(forKey: "PendingFlutterTask") else { return true } // 传递任务给Flutter let channel = FlutterMethodChannel(name: "com.your.app/task-channel", binaryMessenger: window?.rootViewController as! FlutterBinaryMessenger) channel.invokeMethod("executeTask", arguments: ["data": taskData]) // 清空任务标记 groupDefaults?.removeObject(forKey: "PendingFlutterTask") groupDefaults?.synchronize() return true }Flutter侧接收并执行代码
在Dart中监听MethodChannel,收到调用后执行对应逻辑:final MethodChannel _taskChannel = MethodChannel('com.your.app/task-channel'); @override void initState() { super.initState(); _taskChannel.setMethodCallHandler((MethodCall call) async { if (call.method == 'executeTask') { String taskData = call.arguments['data']; // 执行你的Flutter业务逻辑 _processTask(taskData); } }); } void _processTask(String taskData) { // 处理任务的具体代码 }
方案2:iOS 15+ 用BGTaskScheduler调度后台任务
如果你的App只需要支持iOS 15及以上,可以利用BGTaskScheduler让主App在后台执行任务,无需强制唤醒到前台:
配置后台任务权限
在主App的Info.plist中添加BGTaskSchedulerPermittedIdentifiers数组,包含你的任务ID(比如com.your.app.flutter-bg-task)。扩展侧调度后台任务
在通知扩展中写入任务数据后,提交后台任务请求:override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) let groupDefaults = UserDefaults(suiteName: "group.com.your.app") groupDefaults?.set("后台任务数据", forKey: "FlutterBGTaskData") groupDefaults?.synchronize() // 提交后台刷新任务 let taskRequest = BGAppRefreshTaskRequest(identifier: "com.your.app.flutter-bg-task") taskRequest.earliestBeginDate = Date(timeIntervalSinceNow: 1) // 立即执行 do { try BGTaskScheduler.shared.submit(taskRequest) } catch { print("提交后台任务失败: \(error)") } contentHandler(bestAttemptContent ?? request.content) }主App侧注册并处理后台任务
在主App的AppDelegate启动时注册任务处理器,收到任务后触发Flutter代码:func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 注册后台任务 BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.your.app.flutter-bg-task", using: nil) { task in self.handleFlutterBackgroundTask(task: task as! BGAppRefreshTask) } return true } func handleFlutterBackgroundTask(task: BGAppRefreshTask) { let groupDefaults = UserDefaults(suiteName: "group.com.your.app") guard let taskData = groupDefaults?.string(forKey: "FlutterBGTaskData") else { task.setTaskCompleted(success: false) return } let channel = FlutterMethodChannel(name: "com.your.app/bg-task-channel", binaryMessenger: window?.rootViewController as! FlutterBinaryMessenger) channel.invokeMethod("runBackgroundTask", arguments: taskData) groupDefaults?.removeObject(forKey: "FlutterBGTaskData") groupDefaults?.synchronize() task.setTaskCompleted(success: true) }Flutter侧处理任务
同样通过MethodChannel接收任务并执行逻辑,和方案1的Dart代码类似,只需修改对应的Channel名称和方法名。
关键注意事项
- 两种方案都依赖主App未被用户完全杀死:如果App被强制关闭,URL Scheme会直接打开App到前台,而BGTaskScheduler的任务会无法触发。
- BGTaskScheduler的任务执行有严格的时间限制(通常30秒内),确保Flutter代码逻辑简洁高效。
- URL Scheme需要在主App的
Info.plist中配置CFBundleURLTypes,并在扩展的Info.plist中添加LSApplicationQueriesSchemes允许调用该Scheme。
内容的提问来源于stack exchange,提问作者DixieFlatline

