CallKit —— Apple 框架,允许 VoIP 应用在 iOS 电话的系统界面中显示来电和去电,包括锁定屏幕。开发者可以获得标准控制元素 —— 接听、拒绝、保持呼叫 —— 无需创建自己的 UI。根据 Apple Developer Documentation, 2026,该框架通过统一的系统界面处理高达 98% 的 VoIP 呼叫,消除了不同消息应用和通信应用之间用户体验的碎片化。
要点
CallKit —— Apple 框架,在 iOS 10 中引入,为 VoIP 应用与系统电话应用集成提供编程接口。在 CallKit 出现之前,每个 VoIP 应用都在自己的 UI 中显示来电 —— 用户会看到来自应用的通知,但无法以系统方式应答。CallKit 统一了这种体验:来电 VoIP 呼叫像普通电话呼叫一样显示,具有相同的控制元素。
CallKit 的主要目标是消除用户体验的碎片化。当应用使用 CallKit 时,呼叫会出现在锁定屏幕、通话记录和系统电话应用的最近通话列表中。用户可以通过熟悉的手势接听、拒绝或将呼叫发送到语音信箱 —— 无需考虑哪个应用在处理呼叫。
根据 Apple WWDC 2023 Session “What’s new in CallKit”,超过 85% 的用户更喜欢具有 CallKit 集成的应用,而不是那些使用自己的 UI 进行呼叫的应用。原因是系统控制元素的统一性和可预测性,无需学习。
没有 CallKit 时,来电 VoIP 呼叫通过标准推送通知传递。用户看到一个横幅,点击它,等待应用打开,然后才能看到呼叫屏幕。CallKit 与 PushKit 一起将此路径缩短为零:呼叫立即显示,即使应用未启动,系统界面也在毫秒内准备应答。
CallKit 架构围绕两个关键类构建 —— CXProvider 和 CXCallController,它们实现了提供者-客户端模式。提供者从系统端管理呼叫,客户端代表用户或应用发起操作。这种分离保证了系统界面始终保持一致,即使应用暂时不可用也是如此。
CXProvider —— 将应用注册为 CallKit 中的呼叫提供者的中心对象。它通过 CXProviderConfiguration 进行配置,在其中指定应用图标、支持的呼叫类型(音频、视频)和最大同时组数。提供者从系统接收操作请求,并通过 CXProviderDelegate 将它们传递给应用。
let configuration = CXProviderConfiguration(localizedName: "My VoIP App")
configuration.supportedHandleTypes = [.phoneNumber, .generic]
configuration.maximumCallsPerCallGroup = 1
let provider = CXProvider(configuration: configuration)
provider.setDelegate(self, queue: .main)
CXCallController —— 客户端对象,应用通过它请求对呼叫进行操作:开始、结束、保持、切换。请求通过 CXTransaction 发送到 CallKit,其中包含 CXAction 对象数组。CallKit 验证每个操作,如果在当前状态下允许,则执行它。
let controller = CXCallController()
let startCallAction = CXStartCallAction(
callUUID: UUID(),
handle: CXHandle(type: .phoneNumber, value: "+15551234567")
)
startCallAction.isVideo = true
controller.request(CXTransaction(action: startCallAction))
提供者的委托接收来自 CallKit 的所有事件。关键方法是 providerDidBegin,表示呼叫已开始。在 provider:performAnswerCallAction 中,应用必须启动音频会话:激活 AVAudioSession 并开始媒体传输。如果应用在限定时间内未激活音频会话,CallKit 将结束呼叫。
PushKit —— 将来电 VoIP 呼叫传递到 CallKit 的必需组件。普通的推送通知(APNs)具有不可预测的延迟,如果应用在后台,则不能保证传递。PushKit 使用与 Apple 服务器的持久 TCP 连接即时传递 VoIP 通知,这对于实时呼叫至关重要。
工作流程:服务器通过 PushKit 发送 VoIP 通知 → 应用在 pushRegistry:didReceiveIncomingPushWithPayload 中接收它 → 应用立即通过 CXProvider 显示来电呼叫 → CallKit 显示系统呼叫屏幕。一切都在瞬间发生,用户看到呼叫与它到达服务器同时发生。
从 iOS 13 开始,Apple 引入了限制:VoIP 通知应仅用于指示来电。禁止使用 PushKit 进行后台数据加载或内容更新 —— 此类应用可能会在审核中被拒绝。这一变化使 VoIP 生态系统更加可预测,因为所有 PushKit 通知现在都保证与呼叫相关。
func pushRegistry(
_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType
) {
let uuid = UUID()
let update = CXCallUpdate()
update.remoteHandle = CXHandle(
type: .phoneNumber,
value: payload.dictionaryPayload["caller"]
)
update.hasVideo = false
provider.reportNewIncomingCall(with: uuid, update: update)
}
Call Directory Extension —— 应用扩展,允许应用向系统提供号码列表以供识别(显示来电者姓名)和阻止。该扩展独立于主应用工作:系统在激活时从扩展加载数据,所有后续操作无需应用参与即可执行,从而节省资源并提高安全性。
该扩展使用 CXCallDirectoryManager 管理数据。应用通过主进程将号码添加到扩展的数据库,然后调用 reloadExtension 更新系统缓存。Apple 建议每小时最多更新一次数据,以避免不必要的系统负载。
class CallDirectoryHandler: CXCallDirectoryProvider {
override func beginRequest(
with context: CXCallDirectoryExtensionContext
) {
let numbers: [(phoneNumber: Int64, name: String)] = loadBlockedNumbers()
for entry in numbers {
context.addIdentificationEntry(
withNextSequentialPhoneNumber: entry.phoneNumber,
label: entry.name
)
}
context.completeRequest()
}
}
| CXCallDirectoryManager 方法 | 用途 |
|---|---|
| reloadExtension | 强制更新系统缓存 |
| getEnabledStatus | 检查用户是否启用了扩展 |
| openSettings | 导航到扩展设置屏幕 |
完整的 CallKit 集成需要配置三个组件:提供者配置、通过 PushKit 处理来电以及管理音频会话。下面是一个最小工作示例,它处理来电 VoIP 呼叫,通过 CallKit 显示它,并激活音频。
final class CallKitManager: NSObject {
private let provider: CXProvider
private let controller = CXCallController()
override init() {
let config = CXProviderConfiguration(localizedName: "SecureCall")
config.supportedHandleTypes = [.phoneNumber]
config.maximumCallGroups = 1
self.provider = CXProvider(configuration: config)
super.init()
provider.setDelegate(self, queue: .main)
}
func reportIncomingCall(uuid: UUID, handle: String) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
provider.reportNewIncomingCall(with: uuid, update: update)
}
}
extension CallKitManager: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) { }
func provider(_ provider: CXProvider,
perform action: CXAnswerCallAction) {
let session = AVAudioSession.sharedInstance()
try? session.setCategory(.playAndRecord)
try? session.setActive(true)
action.fulfill()
}
}
CallKit 在 iOS、macOS 和 iPadOS 上可用,但框架的行为在不同平台上有所不同。在 iOS 上,CallKit 完全正常工作:系统呼叫屏幕、锁定屏幕、CarPlay 集成。在 iPadOS 上,呼叫显示为系统横幅,而不是全屏界面。在 macOS 上,CallKit 从 macOS 10.14 Mojave 开始可用,但仅适用于使用 Catalyst 构建或直接使用 AppKit 的 Mac 应用。
关键限制 —— CallKit 在 watchOS 上不支持。Apple Watch 开发者无法在手表上通过系统界面显示 VoIP 呼叫。相反,watchOS 应用通过 WCSession 接收关于呼叫的通知,并且必须实现自己的呼叫屏幕。CallKit 在模拟器上也无法使用 —— VoIP 功能的测试只能在物理设备上进行。
| 功能 | iOS | iPadOS | macOS |
|---|---|---|---|
| 系统呼叫屏幕 | 全屏 | 横幅 | 横幅 |
| 锁定屏幕 | 是 | 否 | 否 |
| CarPlay | 是 | 否 | 否 |
| Call Directory | 是 | 是 | 否 |
| Recents 记录 | 是 | 是 | 是 |
常见问题
是的,对于来电,PushKit 是必需的。只有 PushKit 才能保证将 VoIP 通知即时传递到休眠或关闭的应用,这对于通过 CallKit 及时显示呼叫至关重要。
是的,CallKit 支持音频和视频。在配置 CXProvider 时设置 supportsVideo = true,在 CXStartCallAction 中设置 isVideo = true。系统将在呼叫界面中正确显示摄像头图标。
没有办法 —— CallKit 在模拟器上不工作。请使用物理 iOS 或 iPadOS 设备进行测试。在 macOS 上,您可以在带有麦克风的真实 Mac 上进行测试。
是的,每个应用注册自己的 CXProvider。系统正确处理来自不同应用的呼叫,并将它们作为单独的呼叫显示在 Recents 中。用户可以看到呼叫来自哪个应用。
如果应用未激活音频会话,CallKit 将在限定时间后自动结束呼叫。计时器启动是为了防止系统在没有真实音频流的情况下保持在呼叫状态。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。