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

iOS端Linphone集成后,如何在APP关闭时接收SIP来电?

解决iOS后台/锁屏下Linphone来电提醒问题

问题分析

你的代码存在几个关键问题,导致后台/锁屏状态下无法正常触发来电提醒:

  1. 组件生命周期错误:PKPushRegistry是局部变量,控制器销毁后会被释放,无法持续监听VoIP推送;且在推送回调中才创建CXProvider,来不及触发系统来电界面。
  2. 推送Payload格式错误:测试用的curl命令发送的是普通通知格式,不符合VoIP推送要求,VoIP推送不需要aps.alert包裹,应直接携带自定义来电数据。
  3. Linphone与CallKit联动缺失:仅处理了CallKit的基础回调,未关联Linphone的实际通话逻辑,无法建立真实通话连接。

修复步骤

1. 全局管理PushRegistry与CallKit Provider

将PKPushRegistry和CXProvider改为全局变量,确保APP在后台时组件依然存活:

import UIKit
import CallKit
import PushKit

class SplashScreen : UIViewController, CXProviderDelegate, PKPushRegistryDelegate {
    // 改为全局变量,保证生命周期
    private var pushRegistry: PKPushRegistry!
    private var callKitProvider: CXProvider!
    
    // ... 原有代码
    
    override func viewDidLoad() {
        super.viewDidLoad()
        MyApp.currentUIViewController = self
        
        // 初始化VoIP推送监听
        pushRegistry = PKPushRegistry(queue: DispatchQueue.main)
        pushRegistry.delegate = self
        pushRegistry.desiredPushTypes = [PKPushType.voIP]
        
        // 提前初始化CallKit Provider
        let config = CXProviderConfiguration(localizedName: "My App")
        config.includesCallsInRecents = true
        config.supportsVideo = true
        config.iconTemplateImageData = UIImage(named: "callkit-icon")?.pngData() // 设置来电界面图标
        callKitProvider = CXProvider(configuration: config)
        callKitProvider.setDelegate(self, queue: DispatchQueue.main)
    }
    
    // ... 原有代码
}

2. 修正VoIP推送测试命令

使用符合VoIP标准的Payload格式,去掉aps.alert,直接传递来电信息:

curl -v -d '{"callerId":"1001", "callerName":"张三", "callType":"video"}' --http2 --cert YeniCallKit.pem:202148 https://api.development.push.apple.com/3/device/177e7861396ad0256886673d1efb3c82d5adb54e86dc5cfd781356c1672cfa07

3. 完善推送回调逻辑

解析推送数据,初始化Linphone核心(APP被杀死时需重新初始化),并正确调用CallKit来电上报,最后必须执行completion回调:

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    MyApp.writeLog(TAG, "收到VoIP推送")
    
    // 解析来电信息
    let callerId = payload.dictionaryPayload["callerId"] as? String ?? "Unknown"
    let callerName = payload.dictionaryPayload["callerName"] as? String ?? "Someone"
    let isVideo = (payload.dictionaryPayload["callType"] as? String ?? "audio") == "video"
    
    // 初始化Linphone核心(APP被杀死后重启需要)
    if !LinphoneManager.shared.isCoreInitialized() {
        LinphoneManager.shared.initializeCore()
    }
    
    // 上报来电给CallKit
    let callUUID = UUID()
    let update = CXCallUpdate()
    update.remoteHandle = CXHandle(type: .phoneNumber, value: callerId)
    update.localizedCallerName = callerName
    update.hasVideo = isVideo
    
    callKitProvider.reportNewIncomingCall(with: callUUID, update: update) { error in
        if let error = error {
            MyApp.writeLog(TAG, "来电上报失败: \(error.localizedDescription)")
        } else {
            // 通知Linphone处理来电逻辑
            LinphoneManager.shared.handleIncomingCall(uuid: callUUID, callerId: callerId)
        }
        // 必须调用completion,否则系统会限制后续推送
        completion()
    }
}

4. 关联CallKit与Linphone通话操作

在CallKit的接听/挂断回调中,调用Linphone的对应方法:

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
    // 通知Linphone接听来电
    LinphoneManager.shared.answerCall(uuid: action.callUUID)
    action.fulfill()
    MyApp.writeLog(TAG, "接听来电")
}

func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
    // 通知Linphone挂断来电
    LinphoneManager.shared.endCall(uuid: action.callUUID)
    action.fulfill()
    MyApp.writeLog(TAG, "挂断来电")
}

5. 确认APP权限配置

  • 在Info.plist中添加UIBackgroundModes,包含voip和audio两项
  • 确保APP已申请并获取Push Notifications权限
  • 确认签名文件包含Push Notification和VoIP服务权限

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 20:42:32