实现类WhatsApp视频通话通知逻辑,解决PushKit未上报CallKit崩溃问题
iOS VoIP推送+CallKit 未上报来电崩溃问题解决方案
首先明确崩溃的根本原因:iOS 13及以上系统对VoIP类型的PushKit推送做了强制约束,收到推送回调后必须在回调执行结束前向CallKit上报新的来电事件,如果未上报、上报超时、或者上报次数和收到的VoIP推送次数不匹配,系统会直接触发崩溃,这是苹果为了防止开发者滥用VoIP推送做的限制,和你要实现的WhatsApp类来电效果本身不冲突。
核心适配方案
- 明确收到VoIP推送后的强制处理逻辑:只要触发
PKPushRegistry的didReceiveIncomingPushWithPayload回调,不管应用当前是什么状态、这个推送是不是有效来电,都要先向CallKit发起新来电上报请求,再做后续的业务判断 - 针对无效推送(比如重复来电、用户已经在其他端接听/拒接的来电)的处理逻辑:上报来电到CallKit之后,再立即调用结束通话的接口,系统会自动弹出来电后立即挂断的通知,不会影响用户体验,也满足苹果的上报要求
- 如果你的应用需要支持免打扰、黑名单、或者用户手动关闭了来电通知的场景:不能直接不上报CallKit,还是要先上报,再根据配置立即结束通话,同时可以配置结束原因为
CXCallEndedReasonAnsweredElsewhere或者CXCallEndedReasonDeclinedElsewhere,符合业务逻辑也满足系统要求
类WhatsApp来电效果的实现细节
- WhatsApp的实现逻辑也是严格遵守了苹果的PushKit+CallKit约束,没有绕过上报的特殊方法:收到VoIP推送后第一时间上报CallKit,系统会弹出原生的来电锁屏/横幅通知,和你要的效果完全一致
- 视频通话的来电配置:上报CXCallUpdate的时候,把
hasVideo属性设为true,系统的CallKit通知会自动显示视频通话的标识,和WhatsApp的样式一致 - 通话后续状态同步:上报来电成功后再异步和自己的业务服务器校验来电有效性,如果已经失效就立即调结束通话接口,如果有效就等用户操作后再同步状态到服务端
代码示例参考
// 收到VoIP推送后的回调 - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion { if (![type isEqualToString:PKPushTypeVoIP]) { if (completion) completion(); return; } // 第一步:生成唯一的通话UUID,先上报CallKit NSUUID *callUUID = [NSUUID UUID]; CXCallUpdate *update = [[CXCallUpdate alloc] init]; update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:payload.dictionaryPayload[@"callerId"]]; update.hasVideo = [payload.dictionaryPayload[@"isVideo"] boolValue]; update.supportsDTMF = NO; update.supportsHolding = NO; update.supportsGrouping = NO; update.supportsUngrouping = NO; [self.callProvider reportNewIncomingCallWithUUID:callUUID update:update completion:^(NSError * _Nullable error) { if (error) { // 上报失败直接结束通话,避免崩溃 [self.callController reportCallWithUUID:callUUID endedAtDate:[NSDate date] reason:CXCallEndedReasonFailed]; if (completion) completion(); return; } // 第二步:异步校验来电有效性 [self checkCallValidWithCallId:payload.dictionaryPayload[@"callId"] completion:^(BOOL isValid) { if (!isValid) { // 无效来电直接结束 [self.callController reportCallWithUUID:callUUID endedAtDate:[NSDate date] reason:CXCallEndedReasonDeclinedElsewhere]; } if (completion) completion(); }]; }]; }
注意事项
- 不要尝试用普通推送替代VoIP推送实现来电通知,iOS后台情况下普通推送无法拉起应用唤起CallKit,达不到锁屏下直接弹出来电通知的效果
- 所有的VoIP推送回调必须调用completion,也必须对应一次CallKit的来电上报,哪怕后续立即结束也不能跳过上报步骤
- 测试阶段可以用Xcode的Push Notifications调试工具模拟VoIP推送,验证不同场景下的上报逻辑是否正常,避免线上崩溃
内容的提问来源于stack exchange,提问作者Abd Alrahman Alhayek
相关产品推荐
相关产品推荐

