FCM 后台通知在 iOS 中不起作用

FCM background notifications not working in iOS

我在 iOS 上遇到 FCM 通知问题。

当我的应用程序处于前台时(appdelegate 中的回调 didReceiveRemoteNotification 被触发),我收到了成功的通知,但是当应用程序处于后台时我没有收到通知(我收到了在 iOS).

的通知托盘中看不到任何内容

所以,我认为问题出在 FCM 发送的消息的格式上。 我的服务器发送到 FCM 的 json 格式如下:

{  
   "data":{  
      "title":"mytitle",
      "body":"mybody",
      "url":"myurl"
   },
   "notification":{  
      "title":"mytitle",
      "body":"mybody"
   },
   "to":"/topics/topic"
}

如您所见,我的json中有两个块:一个通知块(在后台接收通知)和一个数据块(在前台接收通知)。

我不明白为什么收不到后台通知。 我怀疑块的顺序(如果我把 "data" 块放在 "notification" 块之前会有问题吗?)。

编辑: 有关该问题的更多信息。

这是我的 appdelegate.swift:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate
{
    var window: UIWindow?


    // Application started
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool
    {
        let pushNotificationSettings: UIUserNotificationSettings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
        application.registerUserNotificationSettings(pushNotificationSettings)
        application.registerForRemoteNotifications()

        FIRApp.configure()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: "tokenRefreshNotification:", name: kFIRInstanceIDTokenRefreshNotification, object: nil)

        return true
    }




    // Handle refresh notification token
    func tokenRefreshNotification(notification: NSNotification) {
        let refreshedToken = FIRInstanceID.instanceID().token()
        print("InstanceID token: \(refreshedToken)")

        // Connect to FCM since connection may have failed when attempted before having a token.
        if (refreshedToken != nil)
        {
            connectToFcm()

            FIRMessaging.messaging().subscribeToTopic("/topics/topic")
        }

    }


    // Connect to FCM
    func connectToFcm() {
        FIRMessaging.messaging().connectWithCompletion { (error) in
            if (error != nil) {
                print("Unable to connect with FCM. \(error)")
            } else {
                print("Connected to FCM.")
            }
        }
    }


    // Handle notification when the application is in foreground
    func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
            // If you are receiving a notification message while your app is in the background,
            // this callback will not be fired till the user taps on the notification launching the application.
            // TODO: Handle data of notification

            // Print message ID.
            print("Message ID: \(userInfo["gcm.message_id"])")

            // Print full message.
            print("%@", userInfo)
    }


    // Application will enter in background
    func applicationWillResignActive(application: UIApplication)
    {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }



    // Application entered in background
    func applicationDidEnterBackground(application: UIApplication)
    {
        FIRMessaging.messaging().disconnect()
        print("Disconnected from FCM.")
    }



    // Application will enter in foreground
    func applicationWillEnterForeground(application: UIApplication)
    {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
    }



    // Application entered in foreground
    func applicationDidBecomeActive(application: UIApplication)
    {
        connectToFcm()

        application.applicationIconBadgeNumber = 0;
    }



    // Application will terminate
    func applicationWillTerminate(application: UIApplication)
    {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }


}

我可以在前台接收消息的唯一方法是禁用方法 swizzling,在我的 info.plist.

中将 FirebaseAppDelegateProxyEnabled 设置为 NO

在这种情况下,FCM 文档说我必须在 appdelegate.swift 中实施两种方法:

 - FIRMessaging.messaging().appDidReceiveMessage(userInfo)  in didReceiveRemoteNotification callback
 - FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: FIRInstanceIDAPNSTokenType.Sandbox) in didRegisterForRemoteNotificationsWithDeviceToken callback

但是如果我实现这些功能,即使应用程序处于前台,消息也会停止到达。

我知道这很奇怪。

编辑 2:

当应用程序处于后台时,不会收到通知,但当我打开我的应用程序时,会立即收到相同的通知(方法 didReceiveRemoteNotification 被触发)。

假设您已正确设置所有内容,然后将消息的 prioritynormal 设置为 high 应该会立即显示。这是由于 iOS 捆绑通知和处理它们的方式。您可以阅读 Priority of FCM notifications here。请注意,您不应该真正在生产中使用 high,除非有很好的理由,因为它会消耗电池电量。

这里引用自Apple's docs

The priority of the notification. Specify one of the following values:

10–Send the push message immediately. Notifications with this priority must trigger an alert, sound, or badge on the target device. It is an error to use this priority for a push notification that contains only the content-available key.

5—Send the push message at a time that takes into account power considerations for the device. Notifications with this priority might be grouped and delivered in bursts. They are throttled, and in some cases are not delivered. If you omit this header, the APNs server sets the priority to 10.

您需要像这样将 content_available 属性 设置为 true:

{  
   "data":{  
      "title":"mytitle",
      "body":"mybody",
      "url":"myurl"
   },
   "notification":{  
      "title":"mytitle",
      "body":"mybody",
      "content_available": true
   },
   "to":"/topics/topic"
}

本节中有一个蓝色的注释框,上面写着:https://firebase.google.com/docs/cloud-messaging/concept-options#notifications

我遇到了这个问题,设置了 content_available 属性。解决方案是从 iOS 设备上删除并重新安装该应用程序。

您可能需要添加推送通知权利。为此,请转到您的目标设置,然后单击 "Capabilities" 并打开 "Push Notifications"。

优先级和 content_available(如其他答案中所述)是确保您收到通知的关键要素。测试显示了有趣的结果,所以我想在这里分享它们。

测试结果:Swift3,Xcode8,iOS10

优先级 = "high" => "immediate"(在明显的网络延迟内)接收消息。

Priority = "normal" => 各种结果(一般很快,虽然明显比"high"慢)

content_available = 通知中为真(无负载消息)

  • 前景 = 按预期收到数据
  • 背景 = 按预期收到数据(打开应用程序时)

content_available = 顶层为真(无负载消息)

  • 前景 = 按预期收到数据
  • 背景 = 按预期收到数据(打开应用程序时)

content_available = 通知中为真(消息为 {title/body})

  • 前台 = 收到两次数据
  • 背景 = 收到两次数据(打开应用程序时)

content_available = 顶层为真(带有负载消息)

  • 前台 = 收到两次数据
  • 背景 = 收到两次数据(打开应用程序时)

结论:

  1. 虽然优先级是收不到消息的一个可能原因,但最重要的因素是您必须有 'content_available' 或有效负载消息。
  2. content_available 必须用于 data-only 有效负载(没有它,就不会发送任何消息)。
  3. content_available 不应在包含消息的负载上使用,因为它会导致从 FCM 发送双重消息。
  4. 在顶级或通知中使用 content_available 没有发现差异。

编辑:附加测试结果: - 如果您有消息标题,则必须有消息 body 否则您不会收到警报。

奇怪的是,您会收到振动、徽章和声音,但警告框不会出现,除非您有 body 和标题。

-对于 FCM,当应用程序处于后台或前台且 OS <10 application(_:didReceiveRemoteNotification:) 方法将会触发。

-当应用程序在前台并且OS => 10 userNotificationCenter:willPresentNotification:withCompletionHandler:方法将触发。

-在没有通知组件的情况下发送数据消息时: application(_:didReceiveRemoteNotification:) 方法将会触发。

-使用通知组件发送数据消息时: userNotificationCenter:willPresentNotification:withCompletionHandler:方法将触发。