在后台模式下didReceiveRemoteNotification无法工作

46

我正在处理一款包含大量旧代码的大型应用程序。 目前已经实现了以下内容:

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

问题在于它仅在应用程序处于前台时被调用,或者当用户在应用程序在后台时点击通知时才会被调用。 我尝试实现:

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

但是这个应用的行为没有改变。无论如何,当应用在后台时,此方法不会被调用。可能的问题是什么?

但是应用程序的行为没有改变。无论如何-当应用程序处于后台时,该方法不会被调用。可能的问题是什么?


1
当应用程序在后台运行时,该方法不会被调用是正常的。系统将通过弹出警告视图或横幅通知您有关通知的信息。 - KudoCC
你的设备运行的是什么iOS版本?它至少是7吗?因为“fetchCompletionHandler:”需要最低iOS 7版本。 - Islam
8个回答

84

实现didReceiveRemoteNotificationdidReceiveRemoteNotification:fetchCompletionHandler是正确的方式,但您还需要执行以下操作:

确保注册远程通知,请参见这里的文档

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

    return YES;
}

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

enter image description here

此外,您需要将 "content-available":1 添加到推送通知的负载中,否则如果应用程序在后台时,它将无法被唤醒 (参见此处的文档已更新):

要使推送通知触发下载操作,该通知的负载必须包含有效载荷键,其值设置为 1。当该键存在时,系统会在后台唤醒该应用程序(或将其启动到后台),并调用应用程序委托的 application:didReceiveRemoteNotification:fetchCompletionHandler: 方法。该方法的实现应下载相关内容并将其集成到您的应用程序中。

因此,负载至少应如下所示:

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

7
@godmoney,请查看我的更新答案,你需要在通知负载中添加“content-available”:“1”。 - Baris Akar
2
我已经启用了远程通知的后台模式,并且负载应该包含content-available:1键值对,但不会调用我的后台方法。 - priyanka gautam
1
@priyankagautam 抱歉,我不知道是什么原因导致了你的问题。你需要做的就是按照这个答案中描述的步骤去操作... - Baris Akar
2
iOS 14 也存在同样的问题。当应用程序在后台运行时(非强制关闭),didReceiveRemoteNotification 方法从未被调用。有什么建议吗?我按照确切的步骤添加了 "content-available" : 1 到 aps 中。didReceiveRemoteNotification 只在前台调用。 - Atikul Gazi
1
回来补充一下,在后台接收通知的长时间延迟只出现在APNS沙盒环境中。生产环境下的通知非常快。 - ScottyB
显示剩余9条评论

17
  1. 在应用代理中注册推送通知。
  2. 在应用的功能选项中添加后台模式。
  3. 在发送推送通知时添加 "content-available"="1"(如果您使用的是Firebase,请在从服务器端发送推送通知时将 "content-available"="1" 替换为 "content_available"="true")。

对于Firebase,你确实需要使用"content-available"="1"。使用"content_available"="true"是无效的。 - undefined

8

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

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

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

    }
}

嗨,ZaEem,你找到任何解决方案了吗? - Usman Nisar
1
当应用程序在后台时,只有调用此方法 func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) 才能发送远程推送通知。 - Wilson
@UsmanNisar 我已经为我的问题添加了解决方法,现在它对我来说运行良好。你这边不行吗? - ZaEeM ZaFaR

3
我正在创建一个iOS项目,将使用Firebase Cloud Messaging (FCM) 通过Firebase/APNS通知向iOS设备发送自定义数据元素,从公司服务器传递。首先,我必须理解的是,与Android不同,iOS没有类似的“服务”,能够捕获和保存我发送的信息,无论应用程序是在前台(活动状态),后台(仍在内存中)还是非活动状态(不在内存中)。因此,我必须使用通知消息而不是像我为Android设计的数据消息。经过大量阅读,了解Apple APNS和iOS应用程序与APNS服务器之间的Firebase接口,在stackoverflow和其他网络资源上查看无数帖子后,我终于弄清楚如何满足我的要求。
当服务器(或Firebase控制台,因为FCM默认为通知而不是数据消息)发送Firebase云消息(FCM)通知消息时,它通过APNS传递并在iOS设备上显示为通知。当用户点击通知横幅时,iOS会执行以下操作:如果应用程序未运行/加载,则iOS启动应用程序;如果应用程序已加载/运行但在后台,则iOS将应用程序带到前台;或者如果应用程序在前台(这三种情况),则消息内容随后通过函数application(_ application:UIApplication,didReceiveRemoteNotification userInfo:[AnyHashable:Any],fetchCompletionHandler completionHandler:(UIBackgroundFetchResult) -> Void){}进行传递。
有一件事情可以确定,您必须启用后台模式并检查远程通知,您无需在有效负载中包含{"content-available":1}。
1)通过APNS和Firebase设置,生成和注册证书等,非常简单。
2)在appDelegate的didFinishLaunchingWithOptions中添加:
        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


1

我的设备状态很糟糕。虽然我已经完成了这里提到的所有先决条件,但我不得不重新启动设备才能使其正常工作。


0
我通过在 appDelegate 中添加两个方法来解决这个问题:一个用于 tap,另一个用于 foreground。
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()
    

0

还有一种情况,大多数回复没有识别出来,文档中也没有提到。

在我的情况下,我的设备配置了一个特殊的不常见/不相关的设置。

转到“设置” ->“通用” ->“后台应用程序刷新”

您将看到3个选项:

  1. 关闭
  2. Wi-Fi
  3. Wi-Fi 蜂窝数据

重要!社交网络上有一些病毒视频,建议将其设置为“关闭”或“Wi-Fi”,以节省电池寿命/数据计划。这就是我将其设置为“关闭”的时候,在后台没有调用 didReceiveRemoteNotification 的原因,但令人奇怪的是,当我打开应用程序时它竟然被调用了一次。

即使您将其设置为“Wi-Fi/蜂窝数据”,如果没有互联网,它也不会被调用(显然),但最重要的是,我认为指南应该建议在这种“条件”下,将此小细节涵盖在内,即将其设置为“关闭”或“Wi-Fi”,并处于蜂窝网络中的用户<-这是更常见的情况。

可以通过 UIApplication.shared.backgroundRefreshStatus 检索此条件。

https://developer.apple.com/documentation/uikit/uiapplication/1622994-backgroundrefreshstatus

有趣的是,一旦设备进入低功耗模式,它会自动将状态更改为“关闭”,这将混乱你的逻辑。

奇怪的是,在后台模式下应用程序接收通知时,没有地方可以找到这会产生影响,希望这可以帮到你。 -罗伯托


-1

我正在使用 iOS 14,并且我已经按照接受的answer做了一切,但什么也没有发生。我需要从后台获取didReceiveRemoteNotification中的userInfo,但即使我按下实际通知,它也永远不会被调用。我不得不使用下面的方法,在该方法内部我使用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的正确打印语句。

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