Swift 5中Agora视频通话集成CallKit与PushKit推送问题排查
问题诊断与解决方案
核心问题梳理
你的代码存在几个关键问题,导致实际呼叫时无法触发通知:
PKPushRegistry是viewDidLoad内的局部变量,函数执行完后会被ARC释放,无法持续监听voIP推送。CXProvider在每次收到推送时重新实例化,没有全局持有,可能导致CallKit通知无法正常显示。- 缺少服务器端推送触发逻辑:Agora仅负责音视频通话建立,设备间的呼叫通知需要通过APNs(voIP推送)由你的服务器主动发送给目标设备。
- 未正确处理推送的
completion回调,可能被系统判定为推送处理失败,影响后续推送。
分步修复方案
1. 全局持有关键实例
将PKPushRegistry和CXProvider改为类的全局属性,确保生命周期与ViewController一致:
class VideoCallViewController: UIViewController, CXProviderDelegate, PKPushRegistryDelegate { // 新增全局属性 private var pushRegistry: PKPushRegistry! private var callProvider: CXProvider! private var currentCallUUID: UUID? // 保存当前呼叫的UUID // ... 其他原有代码 }
2. 初始化Push Registry和Call Provider
在viewDidLoad中初始化这两个实例,而非局部创建:
override func viewDidLoad() { super.viewDidLoad() initializeAndJoinChannel() setupPushRegistry() setupCallProvider() } private func setupPushRegistry() { pushRegistry = PKPushRegistry(queue: DispatchQueue.main) pushRegistry.delegate = self pushRegistry.desiredPushTypes = [PKPushType.voIP] } private func setupCallProvider() { let config = CXProviderConfiguration() config.iconTemplateImageData = UIImage(named: "xxx")!.pngData() config.ringtoneSound = "ringtone.caf" config.includesCallsInRecents = false config.supportsVideo = true callProvider = CXProvider(configuration: config) callProvider.setDelegate(self, queue: DispatchQueue.main) }
3. 修复推送接收方法
使用全局的callProvider,并正确调用completion回调:
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { // 从payload中解析呼叫信息(比如对方ID、频道名等) let callerName = payload.dictionaryPayload["caller_name"] as? String ?? "Unknown" let channelId = payload.dictionaryPayload["channel_id"] as? String ?? "" let update = CXCallUpdate() update.remoteHandle = CXHandle(type: .generic, value: callerName) update.hasVideo = true currentCallUUID = UUID() callProvider.reportNewIncomingCall(with: currentCallUUID!, update: update) { [weak self] error in if let error = error { print("Failed to report incoming call: \(error.localizedDescription)") } // 必须调用completion,告诉系统推送已处理 completion() } }
4. 补充服务器端推送逻辑
当发起呼叫时,你的服务器需要:
- 收集并保存目标设备的voIP token(你已在
didUpdate pushCredentials中打印token,需将其上传至服务器) - 向苹果voIP推送服务器发送APNs请求,携带呼叫相关payload(比如频道ID、呼叫者信息)
示例APNs请求payload结构:
{ "aps": { "alert": "有人呼叫你", "sound": "ringtone.caf", "content-available": 1 }, "channel_id": "你的Agora频道名", "caller_name": "呼叫者名称", "caller_uid": "呼叫者UID" }
5. 修复CallKit呼出逻辑
保存呼出的UUID,避免后续调用出错:
func initializeAndJoinChannel() { apiCaller.videoCallToken(videoCall: videoCall) { [self] rslt in switch rslt { case .success(let response): if response.result.success { // ... 原有Agora初始化代码 // 修复呼出逻辑 currentCallUUID = UUID() let startCallAction = CXStartCallAction(call: currentCallUUID!, handle: CXHandle(type: .generic, value: "Person")) let transaction = CXTransaction(action: startCallAction) let controller = CXCallController() controller.request(transaction) { error in if let error = error { print("Failed to start call: \(error.localizedDescription)") } } DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 5) { if let uuid = self.currentCallUUID { self.callProvider.reportOutgoingCall(with: uuid, connectedAt: nil) } } // ... 原有加入频道代码 } // ... 错误处理代码 case .failure(_): self.makeAlert(title: "Hata", message: "Veriler alınamadı!") } } }
6. 验证后台配置
再次确认以下配置:
- 开启
Background Modes中的Voice over IP和Remote notifications - 确保voIP推送证书已正确配置,服务器使用对应环境的证书(开发/生产)
- 测试必须使用真实设备,模拟器无法接收voIP推送
内容的提问来源于stack exchange,提问作者hasancanbayram
相关产品推荐
相关产品推荐

