didReceiveRemoteNotification 不在后台工作

didReceiveRemoteNotification not working in the background

我正在开发一个包含大量遗留代码的大型应用程序。 目前 - 有一个实现:

- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo

问题是只有当应用程序在前台或用户在应用程序在后台时点击通知时才会调用它。 我试图实现:

- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler

但应用程序的行为相同。 在任何情况下 - 当应用程序在后台时不会调用此方法。 可能是什么问题?

实现didReceiveRemoteNotificationdidReceiveRemoteNotification:fetchCompletionHandler是正确的方法,但是你还需要做到以下几点:

确保注册远程通知,请参阅 documentation here:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{    
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];

    return YES;
}

还要确保编辑 Info.plist 并选中“启用后台模式”和“远程通知”复选框:

此外,您需要将 "content-available":1 添加到您的推送通知负载中,否则应用程序在后台时不会被唤醒(参见 documentation here updated):

For a push notification to trigger a download operation, the notification’s payload must include the content-available key with its value set to 1. When that key is present, the system wakes the app in the background (or launches it into the background) and calls the app delegate’s application:didReceiveRemoteNotification:fetchCompletionHandler: method. Your implementation of that method should download the relevant content and integrate it into your app

所以负载至少应该是这样的:

{
    aps = {
        "content-available" : 1,
        sound : ""
    };
}

我遇到了同样的问题。出现通知横幅,但未调用 -application:didReceiveRemoteNotification:fetchCompletionHandler: 方法。对我有用的解决方案是添加 - application:didReceiveRemoteNotification: 方法的实现并将调用转发给 -application:didReceiveRemoteNotification:fetchCompletionHandler::

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {

    self.application(application, didReceiveRemoteNotification: userInfo) { (UIBackgroundFetchResult) in

    }
}
  1. 在应用委托中注册推送通知。
  2. 在应用功能中添加后台模式。
  3. 在发送推送通知时添加 "content-available"="1"(如果您使用的是 firebase,则在发送时将 "content-available"="1" 替换为 "content_available"="true"来自服务器端的推送通知)。

我正在创建一个 iOS 项目,该项目将使用 Firebase 云消息传递 (FCM) 传递自定义数据元素,通过 Firebase/APNS 通知从公司服务器发送到 iOS设备。

我必须了解的第一件事是,与 Android 不同,没有任何类似类型的 'Service' 能够捕获和保存我发送的信息,无论应用程序是否在前台(活动)、后台(仍在内存中)或不活动(不在内存中)。因此,我必须使用通知消息而不是数据消息,就像我为 Android.

设计的那样

经过大量阅读以了解 Apple APNS 和 iOS 应用程序与 APNS 服务器之间的 Firebase 接口,查看了 Whosebug 和其他网络资源上的无数帖子,我终于弄清楚了如何让它工作满足我的要求。

当从服务器(或 Firebase 控制台,因为 FCM 默认为通知而非数据消息)发送 Firebase 云消息传递 (FCM) 通知消息时,它通过 APNS 传送并在 iOS 设备。当用户点击通知横幅时,iOS 执行以下操作:如果应用程序不是 running/loaded iOS 启动应用程序,如果应用程序是 loaded/running 但在后台iOS 将应用程序带到前台,或者如果应用程序在前台(所有三种情况),则消息内容将通过 func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {}.

有一件事是肯定的,你必须启用后台模式并检查远程通知,你不必在有效负载中包含 {"content-available" : 1}。

1) 完成 APNS 和 Firebase 设置,非常简单,生成并注册证书等。

2) 在appDelegate, didFinishLaunchingWithOptions, add:

        Messaging.messaging().delegate = self as? MessagingDelegate

        if #available(iOS 10.0, *) {

            UNUserNotificationCenter.current().delegate = self

            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]

            UNUserNotificationCenter.current().requestAuthorization(
                options: authOptions,
                completionHandler: {_, _ in })
        }
        else {

            let settings: UIUserNotificationSettings =
                UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)

            application.registerUserNotificationSettings(settings)
        }

        application.registerForRemoteNotifications()

3) 然后将这些回调函数添加到appDelegate:

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {

        // Print message ID.
        if let messageID = userInfo["gcm.message_id"] {

            print("\n*** application - didReceiveRemoteNotification - fetchCompletionHandler - Message ID: \(messageID)")
        }

        // Print full message.
        print("\n*** application - didReceiveRemoteNotification - full message - fetchCompletionHandler, userInfo: \(userInfo)")

        myNotificationService?.processMessage(title: userInfo["Title"] as! String
            , text: userInfo["Text"] as! String, completion: { (success) in

                if success {
                    completionHandler(.newData)
                }
                else {
                    completionHandler(.noData)
                }
        })
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {

        completionHandler([.alert, .badge, .sound])
    }

很有帮助:

https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html

https://firebase.googleblog.com/2017/01/debugging-firebase-cloud-messaging-on.html

我正在使用 iOS 14,并且我从接受的 开始做了所有事情,但什么也没做。我需要 didReceiveRemoteNotification 中的 userInfo,但它永远不会从 background 中调用,即使我按下实际通知也是如此。我必须使用下面的方法,在该方法中我使用 UIApplication.shared.applicationState 来检查背景状态。我从那里得到 userInfo

// called when notification is tapped
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

    let userInfo = response.notification.request.content.userInfo

    if let dict = userInfo as? [String: Any] {

        let state = UIApplication.shared.applicationState
        if state == .inactive || state == .background {
                
                print("app is in BACKGROUND - userInfo: ", dict) // do something with dict when app is in the background and user taps the notification
                
        } else {
                
                print("app is in FOREGROUND - userInfo: ", dict) // do something with dict when app is in the foreground and user taps the notification
        }
    }

    completionHandler()
}

假设您已正确设置所有其他内容,当您在应用程序处于 后台 时点击通知时,您将获得带有 userInfo 的正确打印语句.

我的设备状态不佳。尽管我已经完成了此处提到的所有先决条件,但我必须重新启动设备才能使其正常工作。

我通过在 appDelegate 中添加两种方法来解决这个问题,一种用于点击,一种用于前台:

func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult)
                 -> Void) {
    print(">>>NOTIF RECEIVED: ACTION:\(userInfo["action"] ?? "nil") DATA: \(userInfo["data"] ?? "nil")<<<")
    completionHandler(UIBackgroundFetchResult.newData)
}
    
func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse,
                            withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo
    print(">>>TAP NOTIF RECEIVED: ACTION:\(userInfo["action"] ?? "nil") DATA: \(userInfo["data"] ?? "nil")<<<")
    completionHandler()
}

重要先决条件:

  • yourTarget/Signing&Capabilities中添加background modes并勾选remote notifications

  • 检查之前是否完成初始化,在 appDelegate/didfinishLaunchingWithOptions 中像这样:

      FirebaseApp.configure()
    
      UNUserNotificationCenter.current().delegate = self
      Messaging.messaging().delegate = self
      let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
      UNUserNotificationCenter.current().requestAuthorization(
          options: authOptions,
          completionHandler: { _, _ in }
      )
    
      application.registerForRemoteNotifications()