关于swift:FCM后台通知在iOS中不起作用

FCM background notifications not working in iOS

我在iOS上的FCM通知有问题。

当我的应用程序位于前台(触发了appdelegate中的回调didReceiveRemoteNotification)时,我收到成功的通知,但是当应用程序位于后台时,我没有收到通知(我在iOS的通知栏中没有看到任何内容) )。

因此,我认为问题在于FCM发送的消息格式。
我的服务器发送到FCM的json格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
{  
  "data":{  
     "title":"mytitle",
     "body":"mybody",
     "url":"myurl"
   },
  "notification":{  
     "title":"mytitle",
     "body":"mybody"
   },
  "to":"/topics/topic"
}

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

我不明白为什么没有收到后台通知。
我对块的顺序感到怀疑(如果将"数据"块放在"通知"块之前是个问题吗?)。

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

这是我的appdelegate.swift:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
@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中实现两个方法:

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

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

我知道这很奇怪。

编辑2:

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


假设您已经正确设置了所有内容,那么将消息的prioritynormal设置为high应该会使它立即出现。这是由于iOS捆绑通知并处理通知的方式所致。您可以在此处阅读有关FCM通知的优先级的信息。请注意,除非有很好的理由,否则您不应该在生产中真正使用high,因为它会消耗电池电量。

这是Apple文档的参考

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
{  
  "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(如其他答案所述)是确保您收到通知的关键要素。测试显示出有趣的结果,所以我想在这里分享。

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

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

优先级="正常" =>各种结果(通常较快,但明显比"较高"慢)

通知中的content_available = true(无有效载荷消息)

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

content_available =顶层为true(无有效负载消息)

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

通知中的content_available = true(带有消息{title / body})

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

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

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

结论:

  • 尽管"优先级"是可能导致无法接收消息的原因,但"最重要的因素"是您必须具有" content_available"或有效载荷消息。
  • content_available必须用于纯数据有效载荷(如果没有,则永远不会发送消息)。
  • content_available不应在包含消息的有效负载上使用,因为它会导致从FCM发送双重消息。
  • 在顶层或通知中使用content_available时没有发现差异。
  • 编辑:其他测试结果:
    -如果您有味精标题,则必须有味精正文,否则就不会收到警告。

    奇怪的是,您将获得振动,徽章和声音,但是除非您有身体和标题,否则警告框不会显示。


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

    Target Capabilities


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

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

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

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


    当您使用直接FCM频道消息时,您不会在后台收到通知

    这是Firebase文档的一段:

    With the direct channel enabled, the FCM backend uses a reliable message queue to keep track of pending messages when the app is in the background or closed. When the app goes to the foreground and the connection is re-established, the channel will automatically send pending messages to the client until it gets an acknowledgement from the client.

    您可以使用FCM APNs界面在前台和后台接收通知


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