You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS CallKit接收missingCall推送取消来电仅首次生效问题排查

VoIP通话CallKit取消逻辑首次生效后后续失效问题修复

故障根因

问题出在两个核心逻辑缺陷:

  1. 已结束通话残留未清理:调用reportCall结束系统来电后,没有将对应的Call对象从callManager的通话数组中移除,导致已失效的通话记录长期驻留内存。
  2. 通话匹配逻辑命中旧记录:后续收到同用户的来电取消事件时,按userId查找通话的方法会优先命中数组中残留的、已结束的旧通话记录,拿到无效的UUID调用结束接口,无法作用于当前活跃的新通话,因此取消操作失效。

首次操作时数组无残留记录,可以正确匹配到当前活跃通话,因此首次取消成功;重启App后内存中残留记录被清空,又会复现首次成功、后续失效的现象,和描述的异常完全吻合。

其他潜在逻辑缺陷

  • 收到missingCall类型的取消推送时,依然触发了响铃接口ringingApi,会导致取消来电时仍短暂播放铃声,不符合交互预期。
  • 全局callOption的属性在多线程环境下读写无保护,异步调用ringingApi时可能被后续推送的内容覆盖,拿到错误的参数。
  • 存在无效冗余代码:在push回调中提前初始化了CXCallUpdate对象但从未使用。
  • 强制解包过多,推送字段异常时会直接触发崩溃;PushKit回调的completion没有在所有分支保证调用,存在被系统杀进程的风险。
  • 极端网络乱序场景下(取消推送早于对应来电推送到达),会因为找不到对应通话记录导致后续展示的来电无法被取消。

修复方案

1. 结束通话后立即移除对应记录

修改reportTheCallerEndCall方法,结束系统来电后同步删除callManager中的对应记录,保证数组中只存活跃通话:

func reportTheCallerEndCall(userId: String) {
    guard let callIndex = self.callManager.findCallIndexInCallsArray(userId: userId) else { return }
    let targetCall = self.callManager.calls[callIndex]
    // 报告系统结束通话
    provider.reportCall(with: targetCall.uuid, endedAt: Date(), reason: .unanswered)
    // 核心修复:从数组中移除已结束的通话,避免残留
    self.callManager.remove(call: targetCall)
}

注意:需要确认callManager已实现remove(call:)方法,如果没有则补充实现,逻辑为从calls数组中移除UUID匹配的对象即可。

2. 优化PushKit回调逻辑,修正分支判断

调整push回调的执行顺序,取消推送分支不要触发响铃逻辑,移除无用的提前初始化对象,替换强制解包为安全解析,保证completion正常调用:

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    print("didReceiveIncomingPushWith payload")
    
    let payloadDictionary = payload.dictionaryPayload as NSDictionary
    // 当前线程解析完所有参数,避免多线程读写全局变量导致数据错乱
    guard let callData = payloadDictionary.value(forKey: "aps") as? [String: Any],
          let user = callData["user"] as? [String: Any],
          let callerID = user["name"] as? String,
          let sndId = callData["snd_id"] as? String,
          let typeCall = callData["typeCall"] as? String else {
        completion()
        return
    }
    let hasVideo = true
    let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)

    if typeCall == "missingCall" {
        // 取消分支:直接结束对应通话,不触发响铃
        self.endIncomingCall(userId: sndId)
        UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
    } else {
        // 正常来电分支:生成新UUID,展示来电后再触发响铃
        let uuid = UUID()
        self.displayIncomingCall(uuid: uuid, handle: callerID, hasVideo: hasVideo, userId: sndId) { _ in
            UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
        }
        DispatchQueue.global().async {
            self.callOption.ringingApi(otherId: sndId)
        }
    }
    completion()
}

3. 增强鲁棒性的可选优化

  • 修改findCallIndexInCallsArray逻辑,除了匹配userId,增加通话状态判断,只返回未结束的活跃通话记录,进一步避免匹配到残留数据。
  • 如果业务规定同一个用户同时只能有一路来电,可以在displayIncomingCall添加新通话前,先移除该userId下所有已存在的旧记录,从源头避免重复。

内容的提问来源于stack exchange,提问作者Mostafa ALZOUBI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 23:24:15