【问题标题】:How to handle push notification when app was force-quit by user? [iOS]用户强制退出应用程序时如何处理推送通知? [iOS]
【发布时间】:2021-01-27 11:17:56
【问题描述】:

首先,澄清一下……

在提出这个“重复的问题”以确认我的知识是正确的并得出结论之前,我已经对现有资源(媒体、stackoverflow、苹果开发者论坛等)进行了相当多的搜索和阅读。

  1. Will iOS launch my app into the background if it was force-quit by the user?
  2. How to get the push notification payload when force-quit / swipe up to kill the iOS app without tapping on the banner/alert?
  3. https://medium.com/fenrir-inc/handling-ios-push-notifications-the-not-so-apparent-side-420891ddf10b
  4. Handling Push Notifications when App is Terminated
  5. https://developer.apple.com/forums/thread/62005#:~:text=In%20most%20cases%2C%20the%20system,force%20quit%20by%20the%20user

在我们开始之前,只是以非常精确的方式说明这些术语。这就是我提到时所指的内容

  1. 前台 - 应用处于活动状态并正在运行,用户基本上正在与应用交互
  2. 背景 - 用户在交互后点击主页按钮。应用保持在后台,用户可以双击主页按钮并从 App Switcher 中找到应用。
  3. 退出 - 应用程序实际上在后台,但它已被系统本身终止。
  4. Kill - 应用不再处于后台,用户双击主页按钮并将应用从 App Switcher 上滑开。

我正在尝试解决的用例

应用在 BACKGROUND、QUIT 和 KILL 状态接收推送通知,然后执行某些后台操作(更新应用程序徽章 + 将通知存储在设备中)

  1. 作为背景 - 是的,我已经设法通过在 APNS 有效负载中一起发送 content-available = 1 来实现这一点。通知横幅出现,后台操作执行!
  2. 对于 QUIT - 是的,我设法通过在 APNS 有效负载中一起发送 content-available = 1 来实现这一点。通知横幅出现,后台操作执行!
  3. 对于 KILL - 显示通知横幅,但未触发后台操作。

我的问题

  1. 无论何时杀死应用程序,都无法在收到通知时唤醒应用程序以执行任何后台操作?
  2. Whatsapp 等消息传递应用程序如何工作?
  3. 应用强制退出时,我该如何处理?
  • 只有当用户点击通知横幅时,我的应用才能运行这些后台操作(增加徽章计数 + 存储数据)。

  • 否则,如果用户选择点击应用图标来打开我的应用。推送的通知在我的应用中根本不存在,包括徽章数量没有增加。

以下是我用 Whatsapp 测试过的内容

背景状态

  1. 打开 Whatsapp,点击主页按钮(将应用程序保留在后台)
  2. 在设备中发送短信,出现横幅通知
  3. 点击应用图标打开应用,应用中有消息。

强制退出状态

  1. 打开 Whatsapp,双击 Home 键,滑动应用程序
  2. 在设备中发送短信,出现横幅通知。
  3. 点击应用图标打开应用,应用中有消息。

强制退出状态 + WiFi 和蜂窝数据已关闭

  1. 打开 Whatsapp,双击 Home 键,滑动应用程序
  2. 在设备中发送短信,出现横幅通知
  3. 关闭 WiFi 和蜂窝数据已关闭。 (确认并尝试通过 Safari 访问网站)
  4. 点击应用图标打开应用,应用中有消息。

使用 Whatsapp 进行的测试基本上得出结论,可以让您的应用唤醒以执行后台操作(特别是在 FORCE QUIT 状态 + WiFi 和蜂窝数据关闭的情况下)

我能解释的唯一“解释”是,他们使用的是 PushKit 通知框架而不是用户通知框架。

11 月 3 日更新 - 即使在 KILL 状态下,iOS 13 也会唤醒应用程序

显然在 iOS 13 上,就像 @hubsi 在评论中提到的内容以及来自 Apple forum 的一些 cmets。即使应用被用户手动杀死,iOS 13 也会唤醒我的应用。

【问题讨论】:

    标签: ios react-native push-notification notifications apple-push-notifications


    【解决方案1】:

    更新 swift 5.x Plus iOS 14.x

        func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                self.deplinkNotification(userInfo: response.notification.request.content.userInfo )
                   }
    }
    

    更新:除了下面提到的之外,似乎从 iOS 13 开始,应用程序实际上是在收到处于终止状态的推送通知时启动的。 我发现为了触发didReceiveRemoteNotification:fetchCompletionHandler委托,应用程序需要在启动时尽快启动registerForRemoteNotifications (UIApplication)方法(例如在didFinishLaunchingWithOptions中)。 在 React Native 的情况下,与下面所述相反,您实际上可以运行 JS 代码。尽管您必须等待 RN 启动才能运行代码,例如像这样:

        public class func didReceiveRemoteNotification(_ userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
            DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                // some RN bridged method/event
            }
        }
    

    旧答案:

    当在 iOS 上收到推送通知且应用被终止时,有两种方法可以运行代码:

    1. 使用PushKit framework
      注意:Apple 只允许将 PushKit 框架用于 VoIP 和 watchOS 应用程序,因此在没有实际要求的情况下使用它,您的应用程序可能无法通过 Apple 的审核
    2. 使用Notification Service Extension
      使用此扩展,您可以在后台运行(无头)代码,包括“丰富”通知,例如带图片附件

    至于 WhatsApp,我很确定他们正在使用 PushKit(无论如何他们都需要它来处理来电)。虽然我认为您在上次测试中描述的内容也可以使用通知服务扩展:
    在扩展程序中,您可以处理推送的通知,包括其有效负载并将数据(例如聊天消息)写入本地应用程序存储(例如通过应用程序组与您的主应用程序共享)。当应用程序打开时,它会从存储中读取数据(聊天消息在没有数据网络的情况下出现)。

    由于问题被标记为“react-native”:请注意,据我所知,无法在通知服务扩展中调用 RN 桥接 JavaScript 代码。您必须编写本机 (Swift/Obj-C) 代码。

    【讨论】:

    • 感谢您提供建议。我不完全确定 Notification Service Extension + App Groups 在这里是否会有所帮助,尽管我遇到了另一个告诉我同样情况的人。据我到目前为止所阅读的内容,几乎所有内容都在说应用程序在用户手动杀死后不会再次醒来。如果您有任何链接可以分享给我以供阅读,不胜感激?
    • 如果你说的“唤醒应用”是指你杀死的主应用将被启动并进入后台模式,没错,你不能这样做(系统不会自动启动你的应用如果用户强制退出它。 link)。您最多只能执行一些额外的代码(您可以将扩展程序用作迷你无头应用程序)。 30 秒,甚至只有通过非静音推送通知才能实现。虽然事情可能有changed again...
    • 是的,这正是我的意思。用户杀死了应用程序,但我们不能简单地“唤醒应用程序”,这就是操作系统的设计方式。通过这种设计,我们似乎应该利用从 APNS 有效负载返回的“徽章”密钥。对于在应用程序端进行徽章计数的应用程序,当应用程序被手动杀死时,将无法在收到通知时更新徽章计数。无论如何,您的意思是拥有“扩展”,即使应用程序被手动杀死,我们也能够执行一些额外的代码?
    • 是的,扩展始终执行,您可以从中设置徽章计数。尽管我认为您无法从扩展中获取当前值(以递增)。但是您可以通过在设置徽章时将当前计数存储在 userdefaults(通过应用组共享)中来解决此问题
    • @TommyLeong 您补充说 iOS 13 会唤醒您的应用。我通过在没有执行的 didReceiveRemoteNotification:fetchCompletionHandler 中运行代码来测试自己。您能否分享一下它唤醒您的应用程序的详细信息(例如,执行了什么,之后它是否作为后台应用程序出现在“应用程序切换器”中)以及您发送到 APNs 的有效负载?谢谢!
    【解决方案2】:

    这对我们适用于 iOS 和 Android,即使应用被用户明确杀死。

    使用-

    1. https://github.com/zo0r/react-native-push-notification
    2. https://github.com/react-native-push-notification-ios/push-notification-ios (完成这两个软件包所需的完整设置)

    APN 请求

    $url = "https://api.sandbox.push.apple.com/3/device/<device_token>";
    $headers = array(
        "apns-push-type: voip",
        "apns-expiration: 10",
        "apns-topic: com.example.app.voip", // .voip as suffix to bundleID
        "apns-collapse-id: lcall", 
        "Content-Type: application/x-www-form-urlencoded",
    );
    $certificate_file = config('pushnotification.apn.certificate');
    $payloadArray['aps'] = [
        'alert' => [
            'title' => "Calling title",
            'body' => "Calling body",
        ],
        'badge' => 1,
        "content-available" => 1
    ];
    $data = json_encode($payloadArray);
    $client = new Client();
    $response = $client->post($url, [
        'headers' => $headers,
        'cert' => $certificate_file,
        'curl' => [
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
        ],
        'body'=> $data,
    ]);
    

    阅读官方apns-push-type- https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns

    FCM 请求

    $url = "https://fcm.googleapis.com/fcm/send";
    $headers = array(
        'Authorization' => 'key=<fcm_server_key',
        'Content-Type' => 'application/json'
    );
    $payloadArray = [
        "to" => "<device_token>",
        "data" => [
            "title"=> "Calling Title",
            "body" => "Calling Body",
        ]
    ];
    $data = json_encode($payloadArray);
    $client = new Client();
    $response = $client->post($url, [
        'headers' => $headers,
        'curl' => [
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        ],
        'body'=> $data,
    ]);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-26
      • 1970-01-01
      • 1970-01-01
      • 2015-05-26
      • 2016-05-05
      • 1970-01-01
      相关资源
      最近更新 更多