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

На чтение
13 мин
Дата обновления
05.11.2026

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):

Состояние приложенияNotificationDataBoth
ForegroundonMessageReceived()onMessageReceived()onMessageReceived()
BackgroundСистемный трейonMessageReceived()Уведомление — в трей, data — в extras launcher Activity

Отсюда частая путаница: разработчик отправляет notification-сообщение, сворачивает приложение и ждет вызова onMessageReceived(), хотя в фоне Android обычно показывает такое уведомление сам.

Создание проекта Firebase и привязка Android-приложения

  1. Создайте проект в Firebase Console.
  2. Добавьте в него Android-приложение.
  3. Укажите тот же package name, что используется в модуле app.
  4. Скачайте файл 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, аналитику доставки и собственную серверную отправку сообщений.