iOS 13因未将PushKit VoIP回调的来电信息发布到系统中而关闭应用程序

26

升级到iOS beta 13之后,我注意到一个不好的事情:我的应用有时会在接收VoIP推送时崩溃。

在崩溃报告中,我看到了以下信息:

iOS 13 Killing app because it never posted an incoming call to the system after receiving a PushKit VoIP callback 

Fatal Exception: NSInternalInconsistencyException
0  CoreFoundation                 0x1af21b9f0 __exceptionPreprocess
1  libobjc.A.dylib                0x1af7284fc objc_exception_throw
2  CoreFoundation                 0x1af11efec + 
 [_CFXNotificationTokenRegistration keyCallbacks]
3  Foundation                     0x1aeda1330 -[NSAssertionHandler 
 handleFailureInMethod:object:file:lineNumber:description:]
4  PushKit                        0x19caa6b54 -[PKPushRegistry 
 _terminateAppIfThereAreUnhandledVoIPPushes]
5  libdispatch.dylib              0x1afa441ec _dispatch_client_callout
6  libdispatch.dylib              0x1af9f6c6c 
_dispatch_lane_barrier_sync_invoke_and_complete
7  PushKit                        0x19caa5b74 __73-[PKPushRegistry 
 voipPayloadReceived:mustPostCall:withCompletionHandler:]_block_invoke
8  libdispatch.dylib              0x1afa43678 
 _dispatch_call_block_and_release
9  libdispatch.dylib              0x1afa441ec 
  _dispatch_client_callout

10 libdispatch.dylib              0x1af9f61f8 
_dispatch_main_queue_callback_4CF$VARIANT$mp
11 CoreFoundation                 0x1af1992a0 
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
12 CoreFoundation                 0x1af1942a8 __CFRunLoopRun
13 CoreFoundation                 0x1af1937ac CFRunLoopRunSpecific
14 GraphicsServices               0x1ae395180 GSEventRunModal
15 UIKitCore                      0x1b6e60244 UIApplicationMain
16 VOIPProject                    0x1009822d8 main + 25 
(AppDelegate.swift:25)
17 libdyld.dylib                  0x1af6e9e7c start

我不明白如何解决这个问题。每当我收到VoIP推送时,我是否必须发布CallKit来电屏幕?这听起来很疯狂,因为在显示呼入电话的屏幕之前,我会检查通知是否有效。有人可以解释一下我该怎么做吗?


1
根据苹果公司的规定,在iOS 13中,当您收到Voip推送时,应向系统报告电话。如果您不这样做,则操作系统将终止该应用程序。因此,您所看到的是苹果公司所说的,如果您不向操作系统报告来电,将会发生的情况。 - Gruntcakes
2个回答

50
在这个帖子中,来自苹果员工的人解释说:
在iOS 13.0及更高版本中,必须使用CallKit框架报告接收到的VoIP呼叫,并在didReceiveIncomingPush()方法执行完成之前报告,否则系统将终止您的应用程序。
反复未能报告呼叫可能会阻止您的应用程序接收任何更多的呼入通知。
基本上,您不能再为非VoIP消息使用VoIP推送,而需要使用常规推送通知。
这是在WWDC会议“应用后台执行的进展”中宣布的https://developer.apple.com/videos/play/wwdc2019/707/

我一直在寻找如何适应应用程序的变化的答案,我能够收集到的是以下内容:

Voip 推送

当您的应用程序接收到这种类型的推送时,它将需要使用 CallKit 报告新的来电。因此,这种类型的推送将专门用于使用 CallKit 的通话。

建议您将通知的 apns-expiration 设置为 0,这样您就不会收到已经过期的通话的推送并被迫呈现呼叫屏幕。

推送通知

常规推送通知是另一个选项。如果您的服务器具有编写通知文本所需的所有信息,则可以发送不会在后台运行您的应用程序的通知。如果您需要在呈现通知之前修改通知的内容,则可以使用 Notification Service 应用扩展;如果您需要唤醒您的应用程序并在后台执行某些操作,则可以发送静默推送通知。

Notification Service App Extension

要使用此功能,必须将您的通知的 mutable-content 设置为 1。这样,您的扩展将在向用户呈现通知之前接收到通知,允许您更改其内容,并且有 30 秒的时间限制。

缺点是您的应用程序将保持在后台,只允许运行您的扩展。这可能意味着您需要通过使用用户默认值、密钥链或共享整个数据库(如果您的应用程序没有为此做好准备,则可能不是一个简单的任务)来在应用程序和扩展之间共享信息和代码。

静默推送通知

要发送静默推送通知,必须将您的通知的content-available设置为1,并删除其alertbadgesound。此通知将在后台唤醒您的应用程序,并调用您的应用程序委托的didReceiveRemoteNotification

这种选项的缺点相当恼人:

  • 您只有30秒的运行时间。
  • 这些通知必须具有5的apns-priority,这可能会导致它们被分组并以突发方式传递,甚至被限制或未传递。
  • 如果用户强制关闭应用程序,则直到用户再次打开应用程序之前,它将完全忽略所有静默通知。

3
@MehulThakkar 是的,iOS13的这个更改带来了许多复杂性。我仍然对我得到的解决方案不满意,但感觉没有其他办法。在我正在开发的应用程序中,用户可以选择不使用CallKit... 然后我们使用自己的呼叫屏幕并向用户展示通知,以便他打开应用程序并查看呼叫屏幕。然而,我们过去使用VoIP推送来发送此通知,现在我们必须使用常规推送,这将会有延迟,可能会在被叫已经放弃呼叫之后才到达。 - pepsy
2
@MehulThakkar 使用Xcode 10(iOS 12)构建可能暂时避免iOS13中的此限制。我在中国,可以看到微信仍在使用PushKit而没有CallKit(在我强制关闭后仍然可以通过语音呼叫触发,并且在接听时没有闪屏)。如果这是真的,那么它会持续多久?如果这是假的,如何在没有PushKit的情况下实现该效果?据我所知,在强制关闭后,远程静默推送通知将无法工作。 - steven
2
是的,从2020年4月开始,需要使用Xcode 11进行构建。 使用Xcode 11构建部分 https://developer.apple.com/app-store/submissions/ - BadCodeDeveloper
1
@Rubycon 1:不行,你不能这么做。在这种情况下,最好的方法是将通知静音(横幅会被展示,但不会发出声音/振动)。2:必须要测试一下,如果没有人回答,我稍后会回答。 - pepsy
1
@Rubycon 我没有找到第二个问题的解决方案,所以决定发一个新的问题: https://stackoverflow.com/questions/59410726/notification-service-extension-hide-previous-notifications - pepsy
显示剩余8条评论

1

当我尝试使用didReceiveIncomingPushWith的异步版本时,我遇到了相同的崩溃。我使用的代码:

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) async {
    guard type == .voIP else { return }

    let callUpdate = CXCallUpdate()
    let phoneNumber = CXHandle(type: .phoneNumber, value: "handle")
    callUpdate.remoteHandle = phoneNumber

    try? await callProvider.reportNewIncomingCall(with: UUID(), update: callUpdate)
}

当应用处于前台时,它可以正常工作,但在后台崩溃。日志显示由于某些原因它根本没有被调用。

与此同时,完成版本工作正常:

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    guard type == .voIP else { return }

    let callUpdate = CXCallUpdate()
    let phoneNumber = CXHandle(type: .phoneNumber, value: "handle")
    callUpdate.remoteHandle = phoneNumber

    callProvider.reportNewIncomingCall(with: UUID(), update: callUpdate) { error in
        completion()
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接