FCM后台通知在iOS上无法工作

38

我遇到了iOS上关于FCM通知的问题。

当我的应用程序处于前台时(appdelegate中的回调didReceiveRemoteNotification被触发),我可以成功地接收通知,但是当应用程序处于后台时,我无法接收通知(在iOS的通知栏中什么也没有显示)。

因此,我认为问题可能出现在FCM发送的消息格式上。 我的服务器发送给FCM的JSON格式如下:

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

如您所见,我的JSON文件中有两个块:一个通知块(用于接收后台通知)和一个数据块(用于接收前台通知)。

我无法理解为什么无法接收后台通知。我怀疑问题出在块的顺序上(如果将“数据”块放在“通知”块之前会出现问题吗?)。

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

这是我的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:.
    }


}

我能在前台接收消息的唯一方法是禁用方法交换,在我的 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被触发)。


只是出于兴趣,你尝试将优先级设置为高了吗? - Chris
1
尝试添加content_available并设置为true值。如果不行的话,尝试在使用content_available时删除priority。我不是很熟悉APNS的工作原理,但是如果prioritycontent_available同时存在于单个负载中,则有可能会忽略该消息。如果有用请告诉我,好运! - AL.
1
我也遇到了同样的问题,但是我已经将优先级设置为“高”,但这并没有帮助。 - Ryan Cocuzzo
@user2363025 是的!我需要添加推送通知权限。我通过Xcode中的目标设置,然后是功能,打开了“推送通知”来完成这个操作。 - Andrew Stromme
@RyanCocuzzo 你可以尝试我的答案 https://dev59.com/o1oU5IYBdhLWcg3wCjrx#41518242,这个方法对我很有效。你可以试试看吗? - Andrew Stromme
显示剩余6条评论
6个回答

23
假设您已正确设置了一切,将消息的prioritynormal设置为high应该使其立即出现。这是由于iOS捆绑通知和处理它们的方式造成的。您可以阅读有关FCM通知优先级的内容 。请注意,除非有充分理由,否则在生产环境中不应真正使用high,因为它会对电池寿命产生影响。
以下是苹果文档的参考资料

通知的优先级。指定以下值之一:

10-立即发送推送消息。具有此优先级的通知必须在目标设备上触发警报、声音或标记。对于仅包含content-available键的推送通知使用此优先级是错误的。

5-在考虑设备功耗的情况下发送推送消息。具有此优先级的通知可能被分组并以突发方式传递。它们受到限制,在某些情况下可能无法传递。如果省略此标头,则APNs服务器将优先级设置为10。


好的,看起来合理。但是我之前发出去的消息呢?已经两个小时了还没有到达。我的意思是,这些消息是通过开发APNS证书发送的,而不是生产环境下的。 在转发时间方面,开发环境和生产环境的消息有区别吗? - Mark O' Brian
1
假设您已经正确设置了所有内容,那么您应该比那更早地看到它们。但请考虑一下,当您在应用程序处于后台状态时发送通知并且您没有看到它显示出来时,然后打开应用程序,然后该通知立即触发且不再排队。有道理吗?正如我所说,这一切都是基于您已经按照Firebase的指南正确设置了背景通知,并且它按照其应有的方式运行。由于您没有提供太多的代码来进行验证,因此我对我的答案做出了一些强烈的假设。 - Chris
谢谢!我们遇到了相同的问题,原来是优先级高解决了。在没有优先级的情况下,当应用在后台时,它们中的任何一个都不会进入。 - Jer
8
我可以确认问题出在优先级上。当我把优先级设置为高时,我的问题得到了解决。谢谢你们! - Mark O' Brian
@MarkO'Brian,你能发给我有效载荷JSON吗?我没有得到正确的JSON格式,无法使用FCM发送通知。 - Sagar Snehi
显示剩余4条评论

19

2
这个答案对于像我一样忘记放置“notification”对象的人很重要。 - marceloquinta
14
根据 https://firebase.google.com/docs/cloud-messaging/http-server-ref,content_available 键应该与 data、notification、to 处于同一级别,而不是在 notification 内部。 - Peacemoon
@Peacemoon 当我将 content_available: 1 放在与 data 同级时,会出现错误:<FIRMessaging/WARNING> FIRMessaging receiving notification in invalid state 2。当我使用 content-available: 1 时,不再收到警告,但无论哪种方式,我都会在托盘中收到通知并且它们不是静音的。有什么想法吗? - user2363025
2
@user2363025,由于Firebase Messaging处理对象的方式,content_available应该为true而不是1。content_available用于GCM,而content-available用于APNS。 - c0deblooded
谢谢,这个格式对我非常有帮助,让我成功地创建了一个可以在Android和iOS上都能工作的通知。 - Lucy
非常感谢你。你救了我的命。 - Abel Tilahun

12

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

测试结果:Swift 3,Xcode 8,iOS 10

Priority =“ high”=>立即(在明显的网络延迟内)接收消息。

Priority =“ normal”=>各种结果(通常很快,但明显比“ high”慢)

在通知中设置content_available = true时(无有效负载消息)

  • 前台=按预期接收到数据
  • 后台=打开应用程序时按预期接收到数据

在顶层设置content_available = true时(无有效负载消息)

  • 前台=按预期接收到数据
  • 后台=打开应用程序时按预期接收到数据

在通知中设置content_available = true时(带有{标题/正文}消息)

  • 前台=接收到两次数据
  • 后台=打开应用程序时接收到两次数据

在顶层设置content_available = true时(带有有效负载消息)

  • 前台=接收到两次数据
  • 后台=打开应用程序时接收到两次数据

结论:

  1. 虽然优先级是未收到消息的可能原因,但最重要的因素是您必须具有“content_available”或有效负载消息。
  2. 必须在仅使用数据有效负载时使用content_available(如果没有它,则永远不会发送任何消息)。
  3. 不应在包含消息的有效负载上使用content_available,因为它会导致从FCM发送双重消息。
  4. 在顶部或通知中使用content_available没有发现任何区别。

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

奇怪的是,您将会得到振动,徽章和声音,但是除非您也有正文,否则警报框不会显示出来。


9
您可能需要添加推送通知许可。请前往目标设置,点击“能力”,并打开“推送通知”开关。 目标能力

2

-对于FCM,当应用程序在后台或前台运行且操作系统<10时,application(_:didReceiveRemoteNotification:)方法将触发。

-当应用程序在前台运行且操作系统 => 10时,userNotificationCenter:willPresentNotification:withCompletionHandler:方法将触发。

-当发送不带通知组件的数据消息时:application(_:didReceiveRemoteNotification:)方法将触发。

-当发送带有通知组件的数据消息时:userNotificationCenter:willPresentNotification:withCompletionHandler:方法将触发。


从 Firebase 文档来看,直接消息需要调用 func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) 而不是 application(_:didReceiveRemoteNotification:) - Lohith Korupolu

-2

我曾遇到过这个问题,当 content_available 属性被设置时。解决方法是从 iOS 设备上移除并重新安装应用程序。


1
这是什么,在哪里? - Nike Kov

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