APNs vs FCM для push‑уведомлений на iOS и Android
Сравнение APNs и FCM для iOS и Android: жизненный цикл токенов, лимиты payload, ожидания доставки и практический чеклист для поиска пропавших пушей.

Что вы сравниваете (и почему это важно)
APNs (Apple Push Notification service) и FCM (Firebase Cloud Messaging) — это каналы доставки, которые переносят сообщение с вашего сервера на телефон. Они не решают, что приложение делает с сообщением, но сильно влияют на то, дойдёт ли сообщение, как быстро и в каком виде оно должно быть.
Когда говорят «уведомления работают на Android, но не на iOS» (или наоборот), это редко одна единственная ошибка. iOS и Android по‑разному обрабатывают фоновую работу, энергосбережение, разрешения и приоритеты сообщений. То же сообщение может задержаться, быть заменено новым, показано без звука или вовсе не показано, если приложение не пробудится, чтобы его обработать.
Это сравнение фокусируется на тех частях, которые дают больше всего неожиданных проблем в реальном мире: как токены меняются со временем, сколько места занимает payload и как он должен быть структурирован, чего реально ожидать по доставке и почему уведомления кажутся пропавшими.
Это не про выбор интерфейса провайдера push, маркетинговую стратегию или построение полной аналитики. Цель — надёжность и ускорение отладки.
Несколько терминов, которые будут встречаться:
- Токен: адрес устройства, выдаваемый APNs или FCM, на который вы отправляете.
- Тема (topic): групповой адрес (в основном в FCM), на который подписываются многие устройства.
- Канал (channel): категория уведомлений на Android, управляющая звуком, важностью и поведением.
- Collapse key / идентификатор свёртывания: механизм замены старых ожидающих сообщений новым.
- TTL (time to live): сколько времени сообщение может ждать доставки, прежде чем истечь.
Правильно настроенные основы экономят часы догадок, когда «простой пуш» ведёт себя по‑разному на iOS и Android.
Как APNs и FCM работают в общих чертах
APNs и FCM — посредники между вашим сервером и телефоном пользователя. Приложение не может надёжно доставить push напрямую по интернету, поэтому оно доверяет эту работу Apple (APNs) или Google (FCM), у которых уже есть доверенные соединения с устройствами.
Общий поток похож: приложение получает токен, ваш бэкенд отправляет сообщение в push‑сервис с этим токеном, и сервис маршрутизирует его на устройство.
APNs простыми словами
На iOS приложение регистрируется для remote notifications и обычно запрашивает у пользователя разрешение. Apple выдаёт device token. Ваш бэкенд (провайдер) отправляет запрос в APNs с этим токеном и payload. APNs решает, можно ли доставить, и пересылает уведомление на устройство.
Бэкенд аутентифицируется в APNs, обычно через token-based auth (ключ подписи). В старых схемах используются сертификаты.
FCM простыми словами
На Android экземпляр приложения регистрируется в FCM и получает registration token. Ваш бэкенд отправляет сообщение в FCM, и FCM доставляет его на нужное устройство. В зависимости от состояния приложения и типа сообщения FCM может автоматически показать уведомление или передать данные приложению для обработки.
Бэкенд аутентифицируется в FCM с помощью серверных учетных данных (API key или service account).
Что вы контролируете: код приложения, когда запрашивать разрешение, хранение токена, серверную логику и отправляемый payload. Что контролируют Apple и Google: сеть доставки, достижимость, правила троттлинга и множество условий «последней мили», таких как энергосбережение и системные политики.
Жизненный цикл токенов: как они выдаются, обновляются и инвалидируются
Главное отличие в повседневной работе между APNs и FCM — токены не «назначаются раз и навсегда». Обращайтесь с ними как с адресами, которые могут поменяться без предупреждения.
На iOS device token APNs привязан к устройству, вашему приложению и настройкам Apple Developer. Он может поменяться после переустановки приложения, восстановления устройства, некоторых обновлений ОС или при смене окружения push (sandbox против production) во время разработки.
На Android FCM registration token может обновиться при восстановлении приложения на новом устройстве, очистке данных приложения, ротации токенов Google или после переустановки. Приложение должно ожидать события обновления и оперативно отправлять новый токен на сервер.
Простое правило: всегда апдейтите токены (upsert), никогда не делайте «insert и забыть». При хранении токенов сохраняйте достаточно контекста, чтобы избежать дубликатов и неправильной отправки:
- ID пользователя или аккаунта (если применимо)
- bundle/package приложения и окружение
- платформа (iOS/Android)
- значение токена и метка времени «last seen"
- статус согласия (permission granted/denied)
Удаления тоже важны. Обычно вы узнаёте, что токен «умер», по ошибкам доставки, а не по явному сигналу «деинсталляция». Если APNs возвращает ошибку Unregistered (часто со статусом 410), или FCM сообщает NotRegistered/Unregistered, немедленно удаляйте этот токен, чтобы не пытаться отправлять бесконечно.
Одна простая утечка приватности: клиент выходит из аккаунта, а другой пользователь входит на том же телефоне. Если вы не очищаете или не переназначаете токен при logout, можно отправлять уведомления не тому человеку, хотя доставка «работает».
Ограничения payload и различия в структуре сообщений
Практическая разница между APNs и FCM — что влезает в сообщение и как телефон с этим обращается при приёме.
Большинство команд используют небольшой набор полей:
- Заголовок и текст (title, body)
- Счётчик badge (iOS)
- Звук (default или кастомный)
- Пользовательские ключ-значения (например,
order_id,status)
Лимиты размера: держите пуши маленькими
У обоих сервисов есть ограничения по размеру payload, и в лимит включаются ваши кастомные данные. При превышении доставка может провалиться или сообщение работать не так, как вы ожидаете.
Надёжный паттерн — отправлять короткое уведомление плюс идентификатор, затем подтягивать детали с бэкенда:
Пример: вместо отправки полного сводного отчёта по заказу отправьте { "type": "order_update", "order_id": "123" } и пусть приложение запросит детали через API.
Data-only vs notification-поведение
На Android FCM‑сообщение с полем «notification» обычно показывается системой, когда приложение в фоне. Data-only сообщение передаётся коду приложения, но может быть отложено или заблокировано фоновыми ограничениями и настройками батареи.
На iOS алерты (title/body) — это просто, но фоновые обновления строже. Фоновый пуш не гарантирует, что ваш код сразу выполнится. Рассматривайте его как подсказку для обновления, а не как триггер реального времени.
Если вам нужна надёжность, держите payload минимальным, включайте стабильный идентификатор и проектируйте приложение так, чтобы оно сверяло состояние при открытии или возобновлении.
Ожидания по доставке и что может помешать уведомлению
И APNs, и FCM работают по принципу best-effort. Провайдер будет пытаться доставить сообщение, но он не гарантирует, что устройство его покажет.
Достижимость (reachability) — первый ограничитель. Вы отправляете уведомление с TTL/expiry. Если устройство вернётся в сеть после истечения этого окна, пуш отброшен. Если TTL очень большой, пользователь может увидеть устаревший алерт позже, что будет восприниматься как баг.
Приоритет влияет на скорость, но это не бесплатное улучшение. Высокий приоритет может помочь срочным сообщениям дойти быстрее, особенно когда устройство спит. Чрезмерное использование приведёт к троттлингу, расходу батареи или к тому, что ОС начнёт относиться к вашему приложению как к «шумному».
Обе системы поддерживают сворачивание сообщений, чтобы новое заменяло старое. APNs использует collapse identifier, FCM — collapse key. Если вы сворачиваете по чему‑то вроде order_status, пользователь может увидеть только последнее состояние, а не все этапы.
Даже если провайдер успешно доставил сообщение, телефон всё ещё может помешать пользовательскому показу:
- Режим «Не беспокоить» или Focus может заглушать или скрывать алерты
- Системные настройки приложения могут быть отключены или выставлены в тихий режим
- Android‑каналы уведомлений могут быть выключены для конкретной категории
- Ограничения фоновой работы или энергосбережение могут задержать доставку
- ОС может подавлять повторяющиеся похожие алерты
Рассматривайте push как ненадёжный транспорт: храните важное состояние на бэкенде и заставляйте приложение обновлять текущее состояние при открытии, даже если уведомление не пришло.
Разрешения и настройки устройства, влияющие на доставку
Многие «проблемы с доставкой» — на самом деле проблемы с разрешениями и настройками.
На iOS важен первый диалог с запросом разрешения. Если пользователь нажал «Не разрешать», уведомления не появятся, пока он не изменит это в Settings. Даже после разрешения он может отключить Lock Screen, Notification Center, баннеры, звуки или badges. Focus и Scheduled Summary также могут скрывать или откладывать алерты.
На Android требования зависят от версии ОС. В новых версиях требуется runtime‑разрешение на уведомления, поэтому обновление приложения может внезапно остановить показ уведомлений, пока пользователь не подтвердит. Видимость зависит от notification channels. Если канал приглушён или установлен с низкой важностью, пуши будут приходить, но не прерывать пользователя.
Ограничения фоновой работы тоже ломают ожидания. Low Power Mode на iOS и оптимизации батареи на Android могут задерживать фоновые задачи, блокировать фоновый трафик или мешать обработке data-only сообщений.
Чтобы понять, что происходит, логируйте то, что видит устройство, а не только то, что отправил бэкенд:
- Встроенные логи: «permission granted», «token registered», «notification received», «notification displayed»
- Индикаторы ОС: состояние разрешений уведомлений (включено/выключено/важность каналов) и режим батареи
- Колбэки пуша: получил ли приложение сообщение на foreground/background
Даже если ваш бэкенд сделан в no-code инструменте, клиентские логи — то, что отделяет «сообщение не дошло» от «дошло, но подавлено».
Пошагово: как отлаживать пропавшие уведомления
Когда пуш пропадает, рассматривайте цепочку: токен → провайдер → payload → поведение приложения. Симптомы могут быть одинаковы на iOS и Android, поэтому проверяйте одни и те же пункты в порядке:
- Убедитесь, что отправляете на актуальный токен. Сравните токен на сервере с тем, что недавно прислал клиент. Логируйте, когда вы в последний раз получили каждый токен.
- Провалидируйте payload перед отправкой. Держите его в рамках лимитов платформы, используйте обязательные поля и избегайте сломанного JSON. Если отправляете data-only сообщения, убедитесь, что приложение готово их обрабатывать.
- Проверьте учётные данные и окружение провайдера. Для APNs — ключ/сертификат, team, bundle ID и совпадение sandbox vs production. Для FCM — правильные проектные креды.
Затем сузьте круг до содержания сообщения или поведения устройства/приложения:
- Отправьте минимальное тестовое уведомление. Короткий title/body подтвердит, что транспорт работает.
- Проверьте обработчики в приложении и поведение на foreground. Многие «пропавшие» пуши приходят, но не показываются. Некоторые приложения намеренно подавляют баннеры в foreground.
- Меняйте одну переменную за раз. Попробуйте другое устройство, другую версию ОС, Wi‑Fi vs мобильная сеть и другую учётную запись. Если проблема только у одного аккаунта — часто причина в устаревших токенах или ошибках таргетинга на сервере.
Практический паттерн: если iOS‑пользователи жалуются, а Android в порядке, начните с отправки минимального алерта на iOS. Если он работает, сосредоточьтесь на структуре payload и обработке в приложении. Если нет — проверьте токены и креды APNs/окружение.
Частые ошибки, приводящие к тихим отказам
Большинство проблем с push — не провайдеры, а мелкие рассинхронизации между ожиданиями приложения и тем, что APNs/FCM примут или что телефон позволит.
Самая частая ошибка — отправка на токен, который больше не валиден. Токены меняются после переустановки, восстановления или обновления. Если сервер продолжает использовать старый токен, пуши уходят в никуда.
Ещё одна — считать доставку гарантированной. Best-effort значит: поздние или пропущенные сообщения нормальны, когда устройства офлайн или под ограничениями энергопотребления. Для важных событий (обновления заказа, оповещения безопасности) нужен fallback в приложении — например, подтягивание актуального статуса при открытии.
Типичные причины пропаж уведомлений:
- Устаревшие iOS или Android токены, оставшиеся после переустановки/обновления
- Превышение лимитов payload (слишком много данных, большие изображения, длинные строки)
- Ожидание фоновой доставки для silent обновлений и последующий троттлинг ОС
- Смешивание iOS окружений (development vs production), из‑за чего токен не совпадает с APNs endpoint
- Игнорирование пользовательского отказа, Focus/Do Not Disturb, выключенных каналов (Android) или системных разрешений
Пример: ритейл‑приложение отправляет «заказ отправлен» с большим JSON‑блобом истории трекинга. Запрос отправки выглядит нормально, но payload отклонён или усечён, и пользователь ничего не видит. Держите пуш компактным и кладите детали за API.
Быстрый чек‑лист, прежде чем винить APNs или FCM
Перед тем как предполагать, что провайдер виноват, выполните простую проверку:
- Проверьте, что токен корректен для пользователя и устройства. Он должен существовать, быть недавно обновлён и привязан к нужной сессии.
- Убедитесь, что креды провайдера сейчас валидны. Проверьте ключи/сертификаты APNs и креды FCM для правильного приложения/проекта.
- Валидируйте форму и размер payload. Держитесь в лимитах и используйте корректные поля.
- Установите TTL, priority и collapse сознательно. Малый TTL может истечь до появления устройства в сети. Низкий приоритет может задерживать доставку. Collapse может заменить ранние сообщения.
- Разделяйте «сервер принял» и «устройство показало». Сравнивайте логи сервера (request/response/ID сообщения) с клиентскими логами (какой токен использовался, был ли вызван handler).
Затем быстро проверьте устройство: разрешены ли уведомления для приложения, правильно ли настроен канал/категория (частая проблема на Android), не включён ли Focus/Do Not Disturb и нет ли ограничений фоновой работы.
Пример: диагностика пропавшего уведомления об обновлении заказа
Саппорт нажал «Отправить обновление заказа» для заказа №1842. В бэкенде в логах написано «notification sent», но клиент ничего не увидел на iPhone или Android.
Начните с бэкенда. Большинство «пропавших» уведомлений либо не были приняты push‑сервисом, либо были приняты, но позднее отброшены, потому что устройство не могло или не хотело их показать.
Сначала проверки на бэкенде
Ищите единственную, отслеживаемую попытку отправки. Один апдейт заказа должен порождать один push‑запрос. Проверьте:
- Использовался ли самый свежий токен для этого пользователя/устройства.
- Ответ провайдера — success, и вы сохранили код ошибки при отказе.
- Payload соответствует правилам платформы (лимиты по размеру, обязательные поля, валидный JSON).
- Аутентификация валидна (APNs ключ/сертификат и team/bundle IDs или FCM креды).
- Вы таргетируете правильное iOS окружение (sandbox vs production).
Если логи показывают rejection вроде «unregistered/invalid token», это вопрос жизненного цикла токена. Если провайдер принял сообщение, но ничего не пришло на устройство — проверьте тип payload и поведение ОС.
Проверки на телефоне
Убедитесь, что телефон может показывать алерт:
- Уведомления включены для приложения (и разрешены для Lock Screen/Banners).
- Focus/Do Not Disturb или summary не скрывают сообщение.
- Режим энергосбережения не ограничивает фоновые задачи (чаще на Android).
- Состояние приложения соответствует типу сообщения (в foreground обработчики могут «поглотить» алерты).
Частый итог: токен в порядке, но сообщение было data-only (на Android) или не настроено правильно для фоновой обработки на iOS, поэтому ОС не показала алерт. Решение — отправлять правильный тип payload для желаемого поведения (видимый алерт vs фон.обновление) и сохранять чистые логи обновлений токенов и ответов провайдера.
Следующие шаги: как сделать push более надёжным в вашем продукте
Push кажется простым, пока не становится ключевой фичей. Надёжность приходит из того, что вы контролируете: гигиена токенов, дисциплина payload и fallback‑пути.
Планируйте пропуски. Push хорош для «посмотри сейчас», но не должен быть единственным каналом для критичных событий. Встроенный inbox в приложении помогает пользователям наверстать упущенное, а email или SMS покрывают важные действия вроде сброса пароля или проблем с оплатой.
Держите payload лаконичным. Рассматривайте push как подсказку, а не как полное сообщение. Отправляйте тип события и ID, а затем подтягивайте детали через API, когда приложение открывается или получает подходящее фоновое обновление.
Опишите короткий runbook для команды, чтобы отладка была последовательной: состояние opt-in, свежесть токенов, коды ответов провайдера, форма/размер payload и окружение/креды.
Если вы строите на AppMaster (appmaster.io), это может упростить хранение токенов, аудит‑логов и бизнес‑логики, триггерящей отправки, при этом вы всё ещё отправляете нативные iOS и Android приложения, которые корректно работают с APNs и FCM.
Вопросы и ответы
APNs — это служба доставки push-уведомлений Apple для iOS, а FCM — служба доставки Google для Android (и может также адресовать iOS через APNs). Ваше приложение по-прежнему решает, что делать с сообщением, но эти сервисы определяют, как вы аутентифицируетесь, как форматировать payload и какого поведения доставки ожидать.
Рассматривайте токены как изменяемые адреса. Сохраняйте их с указанием платформы и окружения, обновляйте при получении нового значения и удаляйте, когда провайдер говорит, что токен недействителен. Практическое правило — upsert токены и хранить метку «last seen», чтобы быстро находить устаревшие записи.
На iOS токен часто меняется после переустановки, восстановления устройства, некоторых обновлений ОС или при переключении между sandbox и production в процессе разработки. На Android FCM-токен может обновиться после переустановки, очистки данных приложения, восстановления устройства или ротации токенов со стороны Google. Приложение должно слушать события обновления и немедленно отправлять новый токен на сервер.
Держите payload маленьким и рассматривайте его как подсказку. Отправьте короткий title/body (если нужен видимый алерт) и стабильный идентификатор, например order_id, а подробности загружайте через API в приложении. Это помогает избежать лимитов по размеру, уменьшает странные граничные случаи и делает поведение более предсказуемым между платформами.
Notification-поля предназначены для показа пользователю, а data-only — для обработки приложением. На Android data-only сообщения могут быть отложены или заблокированы фоновыми ограничениями и настройками энергосбережения, поэтому они не гарантируют немедленную обработку. На iOS фоновые пуши тоже не гарантируют немедленное выполнение кода — их следует рассматривать как подсказку для обновления, а не как триггер для реального времени.
Нет — доставка осуществляется по принципу best-effort. Даже если APNs или FCM приняли запрос, устройство может быть офлайн, сообщение может истечь по TTL, ОС может затормозить доставку, а настройки пользователя — подавить показ. Проектируйте приложение так, чтобы критическое состояние всегда было корректным при открытии, даже если уведомление не показалось.
Сначала отделите «отправлено» от «показано». Убедитесь, что токен актуален, отправьте минимальный тестовый payload с title/body и проверьте, что используете правильные учетные данные APNs/FCM и (для iOS) нужное окружение. Если провайдер принял сообщение, проверьте настройки телефона: Focus/Do Not Disturb, разрешения для приложения и Android-каналы — уведомление могло быть получено, но подавлено.
На iOS чаще всего причина в отказе в разрешениях, включённых Focus режимах или таргетинге не того APNs-окружения (sandbox vs production). На Android блокируют runtime-разрешение на уведомления в новых версиях ОС, заглушённые или низко-важные каналы уведомлений и агрессивная оптимизация батареи, которая откладывает фоновую обработку. Один и тот же бэкенд-отправ может выглядеть корректным, пока устройство тихо не покажет уведомление пользователю.
TTL контролирует, как долго провайдер хранит попытки доставки, а collapse-параметры решают, заменяет ли новое сообщение старое. Короткий TTL может привести к тому, что уведомление пропадёт, если телефон долго был офлайн; collapse-ключи могут оставить пользователю только последнее обновление вместо всех промежуточных событий. Устанавливайте эти параметры сознательно, исходя из желаемого UX.
Сохраняйте таблицы токенов, правила таргетинга и логи отправки в одном месте, чтобы отслеживать каждую попытку end-to-end. AppMaster помогает централизовать хранение токенов, аудит логов и бизнес-логику, которая триггерит отправки, пока нативные iOS и Android приложения корректно работают с APNs и FCM. Ключ — логировать обновления токенов, ответы провайдеров и клиентскую доставку, чтобы быстро понять, где именно сбой.


