Как подключить push-уведомления в Android через Firebase Cloud Messaging

Firebase Cloud Messaging, или FCM, — стандартный способ добавить push-уведомления в Android-приложение без собственной инфраструктуры доставки. Для минимально рабочей схемы нужны четыре части: проект в Firebase, Android-клиент с SDK, идентификатор экземпляра приложения для адресации сообщений и источник отправки — консоль Firebase или ваш сервер.
Ниже — практическая сборка базовой интеграции: от подключения проекта до показа уведомления с учетом Android 13 и notification channels. После такой настройки можно получить тестовый push, обработать входящее сообщение и не потеряться в типичных особенностях FCM на Android.
AndroidConfigV2 в руководстве Firebase помечен preview/Pre-GA, а не стабильным общедоступным релизом. Условия и поддержка такого режима отличаются; упоминание V2 здесь не является рекомендацией без проверки переносить на него production-приложение. Статус FID-регистрации и устаревание token access оцениваются отдельно от статуса V2.
Когда FCM подходит и как ведут себя сообщения
FCM подходит, если приложению нужно показывать уведомления о событиях, получать data-сообщения для синхронизации или адресовать сообщения конкретной установке приложения.
Важно различать три типа payload:
- notification message — содержит визуальную часть уведомления;
- data message — содержит только данные, а все действия выполняет ваше приложение;
- combined message — сочетает оба варианта.
Следующая таблица относится именно к сообщениям с legacy AndroidConfig (v1), для которых используется onMessageReceived. В актуальной документации AndroidConfigV2 имеет другую схему: для видимых уведомлений используется RemoteNotification, специализированные onNotificationReceived и onBackgroundSyncReceived; автоматический показ возможен и на переднем плане. Не переносите таблицу v1 на V2. В примере ниже разбираем совместно поддерживаемую классическую интеграцию с registration token и AndroidConfig (v1):
| Состояние приложения | Notification | Data | Both |
|---|---|---|---|
| Foreground | onMessageReceived() | onMessageReceived() | onMessageReceived() |
| Background | Системный трей | onMessageReceived() | Уведомление — в трей, data — в extras launcher Activity |
Отсюда частая путаница: разработчик отправляет notification-сообщение, сворачивает приложение и ждет вызова onMessageReceived(), хотя в фоне Android обычно показывает такое уведомление сам.
Создание проекта Firebase и привязка Android-приложения
- Создайте проект в Firebase Console.
- Добавьте в него Android-приложение.
- Укажите тот же
package name, что используется в модулеapp. - Скачайте файл
google-services.jsonи положите его в каталогapp/.
Здесь важна аккуратность: данные Firebase-проекта должны соответствовать именно тому Android-приложению, которое вы собираете. Если конфигурация добавлена не к тому package name или файл google-services.json лежит не в модуле приложения, интеграция будет настроена неверно.
Для клиентской части FCM также нужны Google Play services. По текущему руководству FCM нужен Android 7.0 или выше: физическое устройство с приложением Google Play Store либо эмулятор Android с Google APIs. Наличие Play Store не указано как отдельное обязательное условие именно для эмулятора. Проверьте выбранный образ до отладки доставки.
Подключение Firebase Messaging в Gradle и manifest
Начинаем с существующего Android-проекта, в котором уже настроены Android Gradle Plugin и Kotlin. Для подключения Firebase сначала добавьте Google Services plugin на уровне проекта. В корневом build.gradle.kts фрагмент выглядит так:
plugins {
id("com.google.gms.google-services") version "4.5.0" apply false
}Версия 4.5.0 и BoM 35.1.0 ниже соответствуют опубликованному руководству Firebase, проверенному 4 ноября 2026. Если проект уже управляет этим плагином через pluginManagement или version catalog, используйте единую декларацию в принятой схеме проекта. Далее примените плагин в модуле приложения и добавьте Messaging:
// app/build.gradle.kts
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.gms.google-services")
}
dependencies {
implementation(platform("com.google.firebase:firebase-bom:35.1.0"))
implementation("com.google.firebase:firebase-messaging")
}Дальше добавьте сервис и базовые метаданные в AndroidManifest.xml:
<manifest ...>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application ...>
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_notification" />
<service
android:name=".push.AppFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>И сразу зафиксируйте ID канала в ресурсах, чтобы один и тот же идентификатор использовался и в manifest, и в коде:
<resources>
<string name="default_notification_channel_id">general</string>
</resources>Meta-data default_notification_channel_id задаёт канал по умолчанию для входящих FCM notification messages, когда сообщение не указало канал явно. Локальный NotificationCompat.Builder использует тот ID, который передан ему в коде; соответствующий канал должен быть создан. В нашем приложении намеренно выбран один ресурс general для обоих маршрутов, но совпадение с meta-data не является отдельным общим требованием к любому локальному уведомлению.
Иконка уведомления должна быть подготовлена как status bar icon: простая и монохромная. Иначе вместо нее можно увидеть некорректный белый силуэт.
Notification channel и разрешение на Android 13+
Начиная с Android 8.0 уведомления публикуются в каналах. Канал лучше создать заранее, пока приложение находится на экране:
class MainActivity : AppCompatActivity() {
private val notificationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
Log.d("FCM", "Notification permission granted: $granted")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
createDefaultNotificationChannel()
findViewById<View>(R.id.enableNotifications).setOnClickListener {
askNotificationPermission()
}
}
private fun createDefaultNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelId = getString(R.string.default_notification_channel_id)
val channel = NotificationChannel(
channelId,
"General notifications",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "Основные уведомления приложения"
}
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
private fun askNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED -> Unit
shouldShowRequestPermissionRationale(
Manifest.permission.POST_NOTIFICATIONS
) -> {
AlertDialog.Builder(this)
.setMessage("Разрешить оповещения о новых сообщениях?")
.setPositiveButton("Разрешить") { _, _ ->
notificationPermissionLauncher.launch(
Manifest.permission.POST_NOTIFICATIONS
)
}
.setNegativeButton("Позже", null)
.show()
}
else -> notificationPermissionLauncher.launch(
Manifest.permission.POST_NOTIFICATIONS
)
}
}
}
}В res/layout/activity_main.xml для примера создайте экран с кнопкой enableNotifications; например, такой минимальный корневой элемент:
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/enableNotifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Включить оповещения" />Канал создаётся при открытии Activity, но окно runtime-разрешения в этом примере запускается только после действия пользователя. Для ветки пояснения используется androidx.appcompat.app.AlertDialog.
На Android 13 и выше одного разрешения в manifest недостаточно: нужно запросить runtime-разрешение POST_NOTIFICATIONS. До выдачи разрешения приложение не может показывать обычные уведомления.
Запрашивать его лучше в понятном для пользователя контексте: после нажатия на кнопку подписки, включения оповещений по заказу, входа в чат. В нашем учебном экране запрос привязан к нажатию кнопки включения оповещений; при необходимости пользователь сначала видит объяснение. Не приписываем этому варианту гарантированный рост конверсии. При отказе обычные уведомления остаются запрещены до изменения разрешения.
Есть и практическая причина создавать канал заранее. Для приложений, которые все еще targetSdk 32 или ниже, система привязывает первый permission-диалог к моменту создания первого notification channel. Если канал впервые создается в фоне при получении FCM-уведомления, первые сообщения могут потеряться до следующего запуска приложения.
Получение registration token и отправка на сервер
Следующий фрагмент относится к учебному legacy-сценарию с FCM registration token и тестовой отправке по этому token. В текущем руководстве доступ к registration token помечен deprecated с рекомендацией перехода на Firebase Installation ID. Совместная поддержка старого сценария не делает его рекомендуемым выбором для новой регистрации. Для разбираемого token-сценария получение выглядит так:
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w("FCM", "Fetching FCM registration token failed", task.exception)
return@addOnCompleteListener
}
val token = task.result
Log.d("FCM", "Token: $token")
sendTokenToServer(token)
}Токен не стоит считать постоянным. Он может измениться, например после переустановки приложения, очистки данных или восстановления на новом устройстве. Поэтому нужен обработчик обновления:
class AppFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
Log.d("FCM", "New token: $token")
sendTokenToServer(token)
}
}Ниже — авторские договорённости для серверной интеграции; правила актуализации и удаления регистраций опираются на руководство FCM. Функция sendTokenToServer в фрагментах обозначает ваш асинхронный механизм регистрации на собственном backend, а не готовую функцию SDK. Её контракт — передать регистрацию, сохранить результат и обработать сетевую ошибку; адрес и реализация зависят от проекта.
- в нашем проектном варианте передавать токен по HTTPS в API с проверкой пользовательской сессии; это выбранное правило собственной серверной части;
- хранить токен вместе с меткой времени последнего обновления;
- обновлять эту метку при каждом повторном получении токена от клиента;
- удалять регистрацию, если отправка в FCM возвращает
UNREGISTEREDилиINVALID_ARGUMENTи вы уверены, что сам payload сообщения валиден.
Для legacy registration tokens не ограничивайтесь только callback onNewToken. Руководство FCM рекомендует периодически получать текущий token и обновлять его серверную запись с актуальной меткой времени, даже если значение не изменилось. В проекте это можно включить в собственный процесс синхронизации регистраций; приведённый вызов FirebaseMessaging.getInstance().token используется и для такого повторного получения. Периодический запуск и обработку сетевой ошибки нужно реализовать отдельно от учебного callback.
Руководство по управлению регистрациями указывает совместную поддержку FID и legacy registration tokens, а Android get-started отдельно помечает доступ к token как deprecated и предупреждает о будущем удалении этого метода. Это разные аспекты статуса: работающая совместимость и направление миграции. Старые Instance ID API — ещё одна отдельная устаревшая линия, которую нельзя смешивать с этим переходом. Для нового FID-сценария документация предлагает включить firebase_messaging_installation_id_enabled=true и обрабатывать идентификатор через onRegistered; вместе с клиентом обновляется соответствующая серверная адресация. Приведённые ниже getToken/onNewToken сохранены именно как legacy-пример.
Прием сообщений в FirebaseMessagingService
Для базового приема сообщений достаточно сервиса, который обрабатывает payload и при необходимости показывает локальное уведомление:
class AppFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
sendTokenToServer(token)
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
Log.d("FCM", "From: ${remoteMessage.from}")
if (remoteMessage.data.isNotEmpty()) {
Log.d("FCM", "Data: ${remoteMessage.data}")
}
val title = remoteMessage.notification?.title
?: remoteMessage.data["title"]
?: "Новое сообщение"
val body = remoteMessage.notification?.body
?: remoteMessage.data["body"]
?: "Откройте приложение"
applicationContext.showLocalNotification(title, body)
}
}А сама функция показа уведомления может выглядеть так:
fun Context.showLocalNotification(title: String, body: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
return
}
val channelId = getString(R.string.default_notification_channel_id)
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_stat_notification)
.setContentTitle(title)
.setContentText(body)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.build()
NotificationManagerCompat.from(this).notify(1001, notification)
}Показанные фрагменты согласуют локальный показ: канал берётся из того же ресурса, что и в manifest, а сервис вызывает определённую ниже функцию. Это фрагменты существующего Android-проекта: нужны стандартные импорты Android/AndroidX, подготовленная иконка и собственная реализация sendTokenToServer; отдельный backend здесь не создаётся.
Важно и другое ограничение: onMessageReceived() имеет короткое окно выполнения. Если нужно выполнить что-то длиннее нескольких секунд — например, скачать картинку или сделать несколько сетевых запросов, — лучше передать задачу в WorkManager. Иначе фоновые ограничения Android могут прервать обработку.
Как проверить доставку и где чаще всего ошибаются
После сборки запустите приложение на устройстве, получите token и отправьте тестовое сообщение из консоли Firebase. Для первого теста удобно свернуть приложение: так проще проверить появление уведомления в системном трее.
| Симптом | Что проверить |
|---|---|
| Сообщения не приходят совсем | Лежит ли google-services.json в app/, правильно ли подключен плагин Google Services, используется ли устройство с Google Play services |
onMessageReceived() не вызывается в фоне | Не отправляете ли вы notification message: для него это штатное поведение |
| Уведомление не видно на Android 13+ | Выдано ли runtime-разрешение POST_NOTIFICATIONS |
| На Android 8+ push пришел, но ничего не показалось | Для локального показа: создан ли канал с ID, переданным в Builder; для входящего FCM notification: задан ли нужный канал сообщения либо создан канал по умолчанию из meta-data |
| После времени часть устройств перестала получать push | Обрабатываете ли onNewToken(), периодически получаете ли текущий legacy token с обновлением времени на сервере и удаляете ли подтверждённо невалидные регистрации |
| Локальное уведомление из сервиса не показывается | Есть ли маленькая иконка, не заблокировал ли пользователь уведомления для приложения в системных настройках |
Минимально рабочая схема FCM на Android выглядит так: подключить проект Firebase и SDK, зарегистрировать
FirebaseMessagingService, получить token, отправить его на сервер, создать notification channel и запроситьPOST_NOTIFICATIONSна Android 13+.
Если эта база собрана, дальше можно расширять интеграцию без переделки фундамента: добавлять deep links, topic subscriptions, аналитику доставки и собственную серверную отправку сообщений.



