iOS端flutter_callkit_incoming插件:接听呼叫无法自动唤起App求助
问题描述
使用flutter_callkit_incoming插件实现跨平台呼叫通知,Android端运行正常;iOS端能接收通知,但App处于后台/终止状态时,点击接听无法自动唤起App,手动打开App后呼叫可正常启动,期望点击接听直接唤起App。已在WidgetsBinding.instance.addPostFrameCallback中添加checkAndNavigationCallingPage()方法,使用插件最新版本,附AppDelegate.swift代码。
解决方案
1. 补充CallKit代理实现,处理呼叫动作回调
当前AppDelegate缺少CXProviderDelegate协议实现,这是iOS唤起App并响应CallKit接听/挂断动作的核心逻辑。修改代码如下:
import UIKit import Flutter import flutter_local_notifications import flutter_callkit_incoming import CallKit import AVFAudio import PushKit @main @objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CXProviderDelegate { // 全局CallKit Provider实例 var callKitProvider: CXProvider? override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // 原有代码保持不变 FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in GeneratedPluginRegistrant.register(with: registry) } if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate } GeneratedPluginRegistrant.register(with: self) // 初始化CallKit Provider let callKitConfig = CXProviderConfiguration(localizedName: "你的App名称") callKitConfig.supportsVideo = true callKitConfig.maximumCallsPerCallGroup = 1 callKitConfig.supportedHandleTypes = [.generic] callKitProvider = CXProvider(configuration: callKitConfig) callKitProvider?.delegate = self // Setup VOIP let mainQueue = DispatchQueue.main let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue) voipRegistry.delegate = self voipRegistry.desiredPushTypes = [PKPushType.voIP] return super.application(application, didFinishLaunchingWithOptions: launchOptions) } // MARK: - CXProviderDelegate 实现 // 激活音频会话 func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { do { try audioSession.setActive(true) } catch { print("激活音频会话失败: \(error)") } } // 处理接听动作 func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { // 通过自定义Scheme唤起App guard let url = URL(string: "your-app-scheme://call-answer") else { action.fail() return } UIApplication.shared.open(url, options: [:]) // 通知插件处理接听事件 SwiftFlutterCallkitIncomingPlugin.sharedInstance?.answerCall(action.callUUID.uuidString) action.fulfill() } // 处理挂断动作 func provider(_ provider: CXProvider, perform action: CXEndCallAction) { SwiftFlutterCallkitIncomingPlugin.sharedInstance?.endCall(action.callUUID.uuidString) action.fulfill() } // 原有PushRegistry相关方法保持不变 func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { print(credentials.token) let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined() print(deviceToken) SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken) } func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { print("didInvalidatePushTokenFor") SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("") } func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { print("didReceiveIncomingPushWith") guard type == .voIP else { return } let id = payload.dictionaryPayload["id"] as? String ?? UUID().uuidString let nameCaller = (payload.dictionaryPayload["params"] as? [String: Any])? ["body"] as? String ?? "" let appName = (payload.dictionaryPayload["params"] as? [String: Any])? ["title"] as? String ?? "" let handle = payload.dictionaryPayload["handle"] as? String ?? "generic" let isVideo = payload.dictionaryPayload["isVideo"] as? Int ?? 1 let duration = payload.dictionaryPayload["duration"] as? Int ?? 30000 let ringtonePath = payload.dictionaryPayload["ringtonePath"] as? String ?? "system_ringtone_default" var args: NSDictionary = ["id": id, "nameCaller": nameCaller, "appName": appName, "handle": handle, "type": isVideo, "duration": duration, "ringtonePath": ringtonePath] let data = flutter_callkit_incoming.Data(args: args) data.extra = payload.dictionaryPayload["params"] as? NSDictionary ?? [:] data.iconName = "" data.type = 1; SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true) // 移除延迟,直接调用completion completion() } }
2. 配置App自定义URL Scheme
在Xcode中为App添加自定义Scheme,确保CallKit能通过URL唤起App:
- 打开项目
Info标签页 - 找到
URL Types,点击+添加新类型 Identifier填写任意标识,URL Schemes填写自定义Scheme(比如your-app-scheme,需和代码中保持一致)
3. Flutter端监听CallKit事件
确保Flutter端监听CALL_ACCEPT事件,在App唤起后自动导航到呼叫页面:
FlutterCallkitIncoming.onEvent.listen((event) { switch (event!.name) { case EventAction.ACTION_CALL_ACCEPT: // 导航到呼叫页面 checkAndNavigationCallingPage(); break; // 其他事件处理(如挂断、拒绝) default: break; } });
4. 检查iOS权限配置
确保项目开启必要权限:
- 在
Capabilities中开启Push Notifications和Background Modes,勾选Voice over IP - 在
Info.plist中添加推送权限描述:
<key>NSUserNotificationUsageDescription</key> <string>需要推送通知接收呼叫</string> <key>NSPushNotificationUsageDescription</key> <string>需要VoIP推送接收呼叫</string>
内容的提问来源于stack exchange,提问作者Djai
相关产品推荐
相关产品推荐

