Фоновые уведомления FCM не работают в iOS
У меня проблема с уведомлением FCM на iOS.
Я получаю уведомления с успехом, когда мое приложение находится на переднем плане (обратный вызов didReceiveRemoteNotification
в appdelegate
срабатывает), но я не получаю уведомления, когда приложение находится в фоновом режиме (я ничего не вижу в трее уведомлений iOS).
Итак, я думаю, что проблема в формате сообщения, отправленного FCM. JSON, отправленный моим сервером в FCM, имеет следующий формат:
{
"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:.
}
}
Единственный способ получать сообщения на переднем плане - отключить метод swizzling, установив для FirebaseAppDelegateProxyEnabled значение NO в моем файле info.plist.
В этом случае в документации FCM говорится, что мне нужно реализовать в моем appdelegate.swift два метода:
- FIRMessaging.messaging().appDidReceiveMessage(userInfo) in didReceiveRemoteNotification callback
- FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: FIRInstanceIDAPNSTokenType.Sandbox) in didRegisterForRemoteNotificationsWithDeviceToken callback
Но если я реализую эти функции, сообщения перестают поступать, даже когда приложение находится на переднем плане.
Я знаю, это очень странно.
РЕДАКТИРОВАТЬ 2:
Когда приложение находится в фоновом режиме, уведомление не получено, но когда я открываю свое приложение, то же самое уведомление получено немедленно (метод didReceiveRemoteNotification срабатывает).
6 ответов
Предполагая, что вы все настроили правильно, затем установите priority
сообщения от normal
в high
должен сделать это появиться немедленно. Это связано с тем, как iOS связывает уведомления и обрабатывает их. Вы можете прочитать о Приоритете уведомлений FCM здесь. Обратите внимание, что вы не должны использовать high
в производстве, если нет веских оснований для этого, поскольку у него есть штраф батареи.
Вот ссылка из документации Apple
Приоритет уведомления. Укажите одно из следующих значений:
10 – Отправьте push-сообщение немедленно. Уведомления с таким приоритетом должны вызывать оповещение, звук или значок на целевом устройстве. Ошибочно использовать этот приоритет для push-уведомления, которое содержит только ключ, доступный для содержимого.
5 - отправляйте push-сообщения за раз, принимая во внимание соображения питания для устройства. Уведомления с таким приоритетом могут быть сгруппированы и доставлены пакетами. Они подавляются, а в некоторых случаях не доставляются. Если этот заголовок опущен, сервер APN устанавливает приоритет 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
Приоритет и content_available (как упомянуто в других ответах) являются ключевыми элементами, обеспечивающими получение уведомлений. Тесты показали интересные результаты, поэтому я решил поделиться ими здесь.
Результаты тестов: Swift 3, Xcode 8, iOS 10
Приоритет = "высокий" => "немедленный" (в пределах очевидных сетевых задержек) прием сообщения.
Priority = "normal" => различные результаты (как правило, быстрые, хотя, очевидно, медленнее, чем "high")
content_available = true в уведомлениях (без сообщения полезной нагрузки)
- Передний план = данные получены, как и ожидалось
- Фон = данные получены, как и ожидалось (при открытии приложения)
content_available = true на верхнем уровне (без сообщения полезной нагрузки)
- Передний план = данные получены, как и ожидалось
- Фон = данные получены, как и ожидалось (при открытии приложения)
content_available = true в уведомлениях (с сообщением {title/body})
- Передний план = данные получены ДВАЖДЫ
- Фон = данные, полученные ДВАЖДЫ (при открытии приложения)
content_available = true на верхнем уровне (с сообщением полезной нагрузки)
- Передний план = данные получены ДВАЖДЫ
- Фон = данные, полученные ДВАЖДЫ (при открытии приложения)
ВЫВОДЫ:
- Хотя приоритет является возможной причиной не получения сообщений, наиболее важным фактором является то, что у вас должно быть либо "content_available", либо сообщение полезной нагрузки.
- content_available ДОЛЖЕН использоваться на полезных нагрузках только для данных (без него сообщение никогда не отправляется).
- content_available НЕ СЛЕДУЕТ использовать в полезных нагрузках, которые содержат сообщения, поскольку это приводит к отправке двойных сообщений из FCM.
- Не обнаружено различий в использовании content_available на верхнем уровне или в уведомлениях.
РЕДАКТИРОВАТЬ: Дополнительные результаты тестирования: - если у вас есть заголовок сообщения, у вас ДОЛЖЕН быть текст сообщения, или вы не получите оповещение.
Странной частью этого является то, что вы получите вибрацию, значок и звук, но окно предупреждения не будет отображаться, если у вас нет тела и названия.
Возможно, вам придется добавить право push-уведомлений. Для этого перейдите к настройкам цели, затем нажмите "Возможности" и включите "Push-уведомления".
При использовании прямых сообщений канала FCM вы не можете получать уведомления в фоновом режиме
это абзац из документа Firebase:
При включенном прямом канале серверная часть FCM использует надежную очередь сообщений для отслеживания ожидающих сообщений, когда приложение находится в фоновом режиме или закрыто. Когда приложение выходит на передний план и соединение восстанавливается, канал автоматически отправляет ожидающие сообщения клиенту, пока не получит подтверждение от клиента.
вы можете использовать интерфейс APM FCM для получения уведомлений как на переднем, так и на заднем плане
-Для FCM, когда приложение находится в фоновом режиме или на переднем плане, а приложение OS <10 (_:didReceiveRemoteNotification:) будет запускаться.
-Если приложение находится на переднем плане и OS => 10 userNotificationCenter:willPresentNotification:withCompletionHandler: метод будет запущен.
-При отправке сообщения с данными без уведомления компонент: приложение (_:didReceiveRemoteNotification:) метод будет запущен.
-При отправке сообщения данных с компонентом уведомления: userNotificationCenter:willPresentNotification:withCompletionHandler: метод будет срабатывать.
У меня была эта проблема, с content_available
набор свойств. Решением было удалить и переустановить приложение с устройства iOS.