其他
iOS VoIP 接入系统通话:需要用…
在 iOS app 中实现与系统原生通话 UI 集成的 VoIP 功能,包括来电/去电、VoIP 推送、音频协调和 Call Directory 时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 39/100 ×0.15
适用场景
iOS VoIP 接入系统通话:需要用 特定
解决思路
- 在 Signing & Capabilities 中启用 Voice over IP 后台模式。
- 添加 Push Notifications capability。
- 如需来电识别/拦截,添加 Call Directory Extension target。
- 使用关键类型:CXProvider 向系统报告呼叫并接收呼叫动作;CXCallController 请求呼叫动作;CXCallUpdate 描述呼叫元数据;CXProviderDelegate 处理系统呼叫动作和音频会话事件;PKPushRegistry 注册并接收 VoIP push;PKVoIPPushMetadata 在 iOS 26.4+ 表示 VoIP push 是否必须上报。
- 在 app 启动时创建且只创建一个 CXProvider,并保持其存活至 app 生命周期结束。
- 使用 CXProviderConfiguration 配置 localizedName、supportsVideo、maximumCallsPerCallGroup、maximumCallGroups、supportedHandleTypes、includesCallsInRecents。
- 创建 CXCallController 用于请求呼叫动作。
- 在报告任何呼叫前设置 CXProviderDelegate,例如 provider.setDelegate(self, queue: nil)。
- 使用 CallManager 单例持有 provider 和 callController,并将不可变属性声明为 let 以安全跨并发域共享。
- 收到要求上报的 VoIP 来电推送后,立即向 CallKit 报告来电,让系统显示原生通话 UI。
- 必须在 PushKit completion handler 返回前报告 required calls,否则系统可能终止 app。
- reportIncomingCall(uuid:handle:hasVideo:) 中创建 CXCallUpdate,设置 remoteHandle 为 CXHandle(type: .phoneNumber, value: handle),设置 hasVideo 和 localizedCallerName。
- 用 withCheckedThrowingContinuation 包装 provider.reportNewIncomingCall(with:update:),根据 error resume throwing 或 resume。
- 实现 CXProviderDelegate.providerDidReset(_:),结束所有通话并重置音频。
- 实现 provider(_:perform: CXAnswerCallAction):先 configureAudioSession(),再在 connectToCallServer(callUUID: action.callUUID) 成功时调用 action.fulfill();失败时 provider.reportCall(with: action.callUUID, endedAt: Date(), reason: .failed) 并 action.fail()。
- 实现 provider(_:perform: CXEndCallAction):调用 disconnectFromCallServer(callUUID: action.callUUID) 后 action.fulfill()。
- 发起去电时使用 CXCallController:创建 UUID、CXHandle、CXStartCallAction(call: uuid, handle: handle),设置 startAction.isVideo = hasVideo。
- 将 CXStartCallAction 包进 CXTransaction,并调用 callController.request(transaction) 处理错误。
- 实现 provider(_:perform: CXStartCallAction):configureAudioSession(),开始连接服务器,并调用 provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: Date())。
- 去电服务器连接完成后调用 provider.reportOutgoingCall(with: action.callUUID, connectedAt: Date()),并 action.fulfill()。
- 每次 app 启动都注册 VoIP push,不要懒加载。
- 使用 PKPushRegistry(queue: .main),设置 delegate 为 self,设置 desiredPushTypes = [.voIP]。
- 在 didUpdate pushCredentials 中把 token 转为十六进制字符串(map { String(format: "%02x", $0) }.joined())并发送到服务器。
- iOS 13 SDK+ app 中,每个 report-required VoIP call push 必须在 PushKit completion 前用 CallKit 报告;如果 app 构建在 LiveCommunicationKit 上,则用 LiveCommunicationKit 报告。
- iOS 26.4+ 使用 PKVoIPPushMetadata.mustReport 判断:true 表示必须在 completion 前报告;false 表示无需 CallKit/LiveCommunicationKit 报告。
- 漏掉 required report 可能导致 app 终止;反复失败可能导致 VoIP 投递停止。
- 对 iOS 26.4+ 实现 didReceiveIncomingVoIPPushWith payload metadata completion,先 guard metadata.mustReport,否则 completion() 并 return,否则调用 handleIncomingVoIPPush(payload, completion: completion)。
- 为 iOS 26.0–26.3 和更老部署目标保留 didReceiveIncomingPushWith payload for type completion;guard type == .voIP,否则 completion() 并 return。
- handleIncomingVoIPPush 中创建 callUUID = UUID(),从 payload.dictionaryPayload["handle"] 读取 handle,默认 "Unknown",在 Task 中调用 CallManager.shared.reportIncomingCall(uuid: callUUID, handle: handle, hasVideo: false),完成后调用 completion()。
- 服务端发送 VoIP push 使用短生命周期:apns-expiration 设为 0 或仅几秒。
- 初始 push 唤醒 app 后,挂断和通话详情变更改用 app-server 连接发送,而不是继续发 VoIP push。
- 音频激活边界交给 CallKit:只在 provider(_:didActivate:) 中启动媒体。
- 在 provider(_:didDeactivate:) 和重置路径中停止或拆除媒体。
- provider(_:didActivate:) 中启动 audio engine / WebRTC。
- provider(_:didDeactivate:) 中停止 audio engine。
- configureAudioSession() 中获取 AVAudioSession.sharedInstance(),调用 setCategory(.playAndRecord, mode: .voiceChat, options: [.allowBluetooth, .allowBluetoothA2DP])。
- Call Directory 用于预加载 caller ID/blocking,不要在每次来电时调用 API 查询。
- Call Directory extension 在 beginRequest(with:) 中加载排序后的批量数据。
- beginRequest 中根据 context.isIncremental 分支:增量调用 addOrRemoveIncrementalEntries(to: context),否则调用 addAllEntries(to: context),最后 context.completeRequest()。
- addAllEntries 中准备 blockedNumbers: [CXCallDirectoryPhoneNumber],按升序排列,并用 context.addBlockingEntry(withNextSequentialPhoneNumber: number) 添加。
- addAllEntries 中准备 identifiedNumbers: [(CXCallDirectoryPhoneNumber, String)],按升序,并用 context.addIdentificationEntry(withNextSequentialPhoneNumber: number, label: label) 添加。
- CXCallDirectoryPhoneNumber 存成国家代码加数字的升序值(如 18005551234),不要存成格式化字符串。
- 主 app 使用 CXCallDirectoryManager.sharedInstance;用 getEnabledStatusForExtension(withIdentifier:) 检查启用状态。
- 如果状态不是 .enabled,用 manager.openSettings 打开 Call Blocking & Identification 设置。
- 数据变化后用 manager.reloadExtension(withIdentifier:) 重新加载扩展。
- 在假定扩展 active 前先检查 getEnabledStatusForExtension(...)。
- 将 APNs auth-key rotation 和普通 remote-notification 设置交给 push-notifications 处理。
- 修复:required VoIP push 被当作 data-only:按版本规则报告,在 completion 前报告,再调用 PushKit completion handler。
- 修复:Answer action 在媒体/服务器就绪前 fulfill:连接期间保持 pending,就绪时 fulfill,失败时 fail 并 report .failed。
- 修复:媒体在 provider(_:didActivate:) 前启动:如有需要可提前准备,但只在 activation 后启动,并在 deactivation/reset 时停止。
- 修复:action 路径从不调用 fulfill() 或 fail():为 success、cancellation、timeout、network-error 路径各给一个终止 action。
- 修复:忽略 token refresh:每次 didUpdate pushCredentials 都把 token 发送到服务器。
- 修复:Call Directory 每次来电联网:预加载排序条目并 reload extension。
- 检查:VoIP background mode 已在 capabilities 中启用。
- 检查:单例 CXProvider 在 app 启动时创建并 retain。
- 检查:CXProviderDelegate 在报告任何 calls 前设置。
- 检查:iOS 26.4+ PushKit 路径在 mustReport 为 true 时报告,为 false 时可跳过。
- 检查:iOS 13 SDK+ PushKit VoIP call pushes 在 completion 前报告给 CallKit。
- 检查:VoIP APNs 请求使用 apns-expiration 为 0 或仅几秒。
- 检查:初始 push 后,hangups 和 detail updates 使用 app-server connection。
- 检查:每个 provider delegate action 都调用 action.fulfill() 或 action.fail()。
- 检查:CXAnswerCallAction 仅在 call server/media connection ready 后 fulfill。
- 检查:Audio engine 仅在 provider(_:didActivate:) callback 后启动。
- 检查:Audio engine 在 provider(_:didDeactivate:) callback 中停止。
- 检查:Audio session category 设为 .playAndRecord 且 mode 为 .voiceChat。
- 检查:每次 didUpdate pushCredentials callback 都把 VoIP push token 发送到服务器。
- 检查:PKPushRegistry 每次 app 启动都创建,不懒加载。
- 检查:Call Directory 数据预加载,不按 incoming call 获取。
- 检查:CXCallDirectoryPhoneNumber 记录为国家 calling code + digits。
- 检查:CXCallDirectoryManager 使用 status check、reload、settings-opening APIs。
- 检查:CXCallUpdate 填充 localizedCallerName 和 remoteHandle。
- 检查:Outgoing calls 报告 startedConnectingAt 和 connectedAt 时间戳。
- 检查:iOS 26 call translation 在 mute 期间保持 upstream audio active。
- 检查:Encrypted metadata filtering 提及 notification service extension entitlement。
适用边界 · 注意事项
- 非 iOS 平台或不需要接入系统原生通话 UI/来电识别时。
- 只需要普通 APNs 远程通知或数据同步,不需要 VoIP 通话时。
- 无法配置 VoIP 后台模式、Push Notifications 或 Call Directory Extension target 时。