Реализация Silent Push Notifications в мобильном приложении
Silent push — уведомление без звука, без баннера, без взаимодействия пользователя. Приходит в фоне, будит приложение, даёт ему несколько секунд на выполнение кода. Используется для фоновой синхронизации контента, инвалидации кеша, обновления badge counter без открытия приложения.
iOS: Background Fetch через Silent Push
На iOS silent push требует двух вещей: флага content-available: 1 в payload и включённой Background Mode «Remote notifications» в Xcode Capabilities.
Payload APNs:
{
"aps": {
"content-available": 1
},
"sync_type": "messages",
"last_known_id": "msg_8823"
}
Без alert, без sound — чистый фоновый вызов. iOS вызовет:
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
guard let syncType = userInfo["sync_type"] as? String else {
completionHandler(.noData)
return
}
Task {
do {
let hasNewData = try await SyncManager.shared.sync(type: syncType)
completionHandler(hasNewData ? .newData : .noData)
} catch {
completionHandler(.failed)
}
}
}
Критический момент: iOS даёт около 30 секунд на выполнение. Если за это время completionHandler не вызван — iOS принудительно завершит фоновую задачу. Кроме того, iOS не гарантирует доставку silent push при низком заряде батареи (Low Power Mode) или когда пользователь принудительно закрыл приложение.
Принудительное закрытие — главная боль. Force quit через iOS task switcher полностью блокирует silent push до следующего ручного открытия приложения. Это документированное поведение, обойти нельзя.
Android: FCM Data Message и WorkManager
На Android нет прямого аналога «silent push» — есть FCM Data Message, который всегда попадает в FirebaseMessagingService.onMessageReceived независимо от состояния приложения (при условии, что оно не убито системой Doze).
class AppFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
val syncType = message.data["sync_type"] ?: return
// Запускаем WorkManager задачу — короткая, с гарантией выполнения
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(workDataOf("sync_type" to syncType))
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()
WorkManager.getInstance(applicationContext).enqueue(workRequest)
}
}
setExpedited() — запрос на немедленное выполнение. Android 12+ требует setForegroundAsync внутри expedited worker или foreground service, иначе ANR при долгой операции.
Doze Mode — Android ограничивает фоновую активность на устройствах без зарядки. FCM использует high-priority сообщения для обхода Doze, но нужно явно указать priority: "high" при отправке через FCM:
{
"message": {
"token": "device_fcm_token",
"android": {
"priority": "HIGH"
},
"data": {
"sync_type": "messages",
"payload": "{...}"
}
}
}
Использование для badge counter
Популярный кейс — обновить цифру на иконке приложения без показа уведомления:
// iOS — через silent push
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if let badge = userInfo["badge"] as? Int {
UNUserNotificationCenter.current().setBadgeCount(badge) { _ in }
}
completionHandler(.newData)
}
На Android badge обновляется через ShortcutBadger (сторонняя библиотека) или через NotificationManagerCompat с setNumber(). Единого API нет — каждый лончер (Samsung, Xiaomi, Huawei) имеет свой механизм.
Ограничения и quota
iOS 13+ ввёл BGTaskScheduler и background processing quota. Если приложение слишком часто запрашивает фоновое выполнение и не приносит пользы пользователю (по мнению iOS) — система начинает throttle-ить вызовы. Completio нHandler с .noData в ответ на silent push, где данных нет — важен именно для корректной работы системы квот.
Сроки
Настройка silent push для iOS (Background Modes, handler) и Android (FCM Data Message + WorkManager), покрытие граничных случаев (Doze, force quit, quota) — 3–5 рабочих дней.







