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

FCM помогает доставить сообщение экземпляру Android-приложения, но успешное подключение Firebase ещё не означает видимое уведомление. Нужно отдельно проверить регистрацию, разрешение пользователя, канал и способ обработки выбранного типа сообщения.
Ниже — авторская учебная сборка базового сценария на Kotlin. Важная граница на 4 ноября 2026 года: текущая справка Firebase уже предлагает регистрацию через Firebase Installation ID и AndroidConfigV2, а доступ к registration token отмечает deprecated. При этом описанный в Console тест всё ещё использует registration token. Поэтому практический тест ниже относится к совместимому legacy AndroidConfig, а различия нового пути названы отдельно. Не переносите его таблицу callback на V2.
AndroidConfigV2 в руководстве Firebase помечен preview/Pre-GA, а не стабильным общедоступным релизом. Условия и поддержка такого режима отличаются; упоминание V2 здесь не является рекомендацией без проверки переносить на него production-приложение. Статус FID-регистрации и устаревание token access оцениваются отдельно от статуса V2.
Выбрать назначение push и режим интеграции
В нашем примере приложение получает извещение об обновлении программы курса. Push не заменяет хранение состояния: пользователь должен иметь возможность увидеть актуальную информацию в самом приложении. Не стройте учебную механику так, будто любое уведомление гарантированно увидят немедленно: разрешения, настройки и состояние устройства проверяются отдельно.
Для выбора канала в нашем учебном приложении примем такие правила: push используем как внешний сигнал об обновлении, когда пользователь не находится на нужном экране; сам список доступных уроков получаем и храним через обычную логику приложения. Если требуется передать документ или обеспечить последующее самостоятельное чтение, предусматриваем личный кабинет либо отдельную отправку документа, а не только карточку push. Если экран уже открыт, обновление его данных проектируем отдельно от системного уведомления. Это авторские критерии данного продукта, не обещание гарантированной доставки другого канала. Для новой архитектуры отдельно изучите FID, а preview V2 оценивайте с учётом его стадии и требований проекта. В совместимом сценарии этой статьи используем getToken/onNewToken и legacy onMessageReceived, поскольку это позволяет пройти конкретный Console-тест из справки. Это осознанный ограниченный пример, а не утверждение, что deprecated API предпочтителен для нового продукта.
Подключить Android-приложение к правильному проекту
- В Firebase Console создайте проект или выберите существующий, добавьте Android-приложение.
- Укажите фактический applicationId собираемого варианта. Другой suffix у debug-варианта нужно учитывать отдельно.
- Скачайте google-services.json и поместите его в модуль app, следуя структуре проекта.
- Добавьте Google services plugin и Firebase Messaging, синхронизируйте Gradle.
На дату разбора руководство setup показывает Google services plugin 4.5.0 и Firebase BoM 35.1.0. В существующие plugins-блоки добавляются соответствующие строки, а не второй самостоятельный plugins-блок в тот же файл:
// Корневой plugins-блок
id("com.google.gms.google-services") version "4.5.0" apply false
// plugins-блок модуля app
id("com.google.gms.google-services")
// dependencies модуля app
implementation(platform("com.google.firebase:firebase-bom:35.1.0"))
implementation("com.google.firebase:firebase-messaging")Это фрагменты конфигурации, не замена всего Gradle-файла. Сохраняйте совместимость своего Android toolchain; используемые ниже NotificationCompat и Activity Result API также требуют соответствующих AndroidX-зависимостей приложения. Для проверки используйте устройство с необходимыми Google-компонентами либо подходящий эмулятор, описанный в руководстве Firebase, а не любой случайный образ.
Зарегистрировать service и разрешение
В Manifest добавьте разрешение на уровне manifest и service внутрь application. Имя класса в примере — AppMessagingService, package согласуйте со своим приложением:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Внутри application -->
<service android:name=".AppMessagingService" android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>Для Android 13+ обычных уведомлений недостаточно только декларации: требуется runtime-разрешение. Запросите его в понятном пользователю контексте, например по кнопке «Включить уведомления об обновлениях». В Activity зарегистрируйте launcher, а при необходимости объяснения используйте shouldShowRequestPermissionRationale. Пример методов внутри ComponentActivity:
private val notificationPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
Log.d("FCM_TEST", "Notification permission granted: $granted")
}
private fun askNotifications() {
if (Build.VERSION.SDK_INT < 33) return
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED) return
if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
AlertDialog.Builder(this)
.setMessage("Уведомления сообщают об обновлениях программы курса.")
.setPositiveButton("Разрешить") { _, _ ->
notificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
}
.setNegativeButton("Не сейчас", null)
.show()
} else {
notificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}Нужны обычные импорты android.Manifest, android.os.Build, android.content.pm.PackageManager, android.app.AlertDialog, android.util.Log, ContextCompat и ActivityResultContracts. Вызовите askNotifications из обработчика выбранной кнопки. При отказе не делайте вид, что разрешение получено; для проверки посмотрите настройки приложения и канала. Не вызывайте системный запрос бесконечным циклом.
Канал и локальное отображение
На Android 8+ уведомление привязано к каналу. Создадим общий помощник DemoNotifications.kt. В проекте заранее добавьте корректный small icon с именем ic_stat_notice. Без такого ресурса пример не скомпилируется; это часть подготовки приложения.
import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
object DemoNotifications {
private const val CHANNEL = "course_updates"
fun createChannel(context: Context) {
if (Build.VERSION.SDK_INT >= 26) {
val channel = NotificationChannel(
CHANNEL, "Обновления курса", NotificationManager.IMPORTANCE_DEFAULT
)
context.getSystemService(NotificationManager::class.java)
.createNotificationChannel(channel)
}
}
fun show(context: Context, title: String, text: String) {
if (Build.VERSION.SDK_INT >= 33 &&
ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) return
createChannel(context)
val notification = NotificationCompat.Builder(context, CHANNEL)
.setSmallIcon(R.drawable.ic_stat_notice)
.setContentTitle(title)
.setContentText(text)
.build()
NotificationManagerCompat.from(context).notify(1001, notification)
}
}Вызовите createChannel при старте Activity, чтобы пользователь мог увидеть канал в настройках. show повторяет создание перед показом. ID 1001 выбран автором для одного учебного уведомления; повторный показ с тем же ID обновит его, а не создаст независимую бесконечную ленту. Этот помощник показывает уведомление, но пока не реализует переход на специальный экран по нажатию.
Registration token и legacy callback
Для Console-теста получите token в Activity. Справка помечает этот доступ deprecated; используем его только в объявленной совместимой лаборатории. Не публикуйте идентификатор устройства в статье или общедоступном логе:
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
if (task.isSuccessful) {
if (BuildConfig.DEBUG) Log.d("FCM_TEST_TOKEN", task.result)
} else {
Log.w("FCM_TEST", "Registration failed", task.exception)
}
}В сервисе покажем локальное уведомление при legacy-вызове. Для класса потребуются импорты FirebaseMessagingService, RemoteMessage и Log; DemoNotifications находится в package приложения:
class AppMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
if (BuildConfig.DEBUG) Log.d("FCM_TEST_TOKEN", token)
}
override fun onMessageReceived(message: RemoteMessage) {
DemoNotifications.show(
this,
message.notification?.title ?: "Обновление курса",
message.notification?.body ?: message.data["text"].orEmpty()
)
}
}Отладочная печать — не серверная синхронизация. Если backend адресует устройства, передавайте актуальный идентификатор по своему защищённому протоколу, обновляйте его при изменении и поддерживайте свежесть регистрации. Не полагайтесь только на единственное значение, сохранённое при установке. Управление такими записями подробно рассматривается в руководстве Firebase по registration management.
Проверить разные типы сообщения и состояние приложения
| Legacy AndroidConfig | Foreground | Background |
|---|---|---|
| notification | onMessageReceived | Системная панель уведомлений |
| data | onMessageReceived | onMessageReceived с учётом ограничений обработки |
| notification + data | onMessageReceived | Уведомление в панели, data — в extras открывающего Activity intent |
Это таблица только legacy v1. В AndroidConfigV2 для пользовательских уведомлений применяется RemoteNotification, специальные callbacks и автоматическое отображение в foreground и background. Не добавляйте поверх V2 ещё один ручной notify по старой схеме: сначала изучите соответствующий контракт. Для регистрации через FID справка описывает флаг firebase_messaging_installation_id_enabled и callback onRegistered; это отдельный переход, а не новое название переменной token.
Для теста из Console установите и запустите приложение, разрешите уведомления, затем отправьте его в фон. В Messaging создайте notification campaign, выберите Send test message, вставьте registration token и нажмите Test. Ожидание — уведомление на целевом устройстве в фоне. Затем проверьте foreground отдельно и объясните разницу через таблицу, вместо вывода «в фоне сервис сломан».
Если результата нет, разделить проблему на этапы
Сначала проверьте applicationId и Firebase-проект, успешность Gradle-синхронизации и получения идентификатора. Затем разрешение, настройки канала и состояние приложения. После этого — тип сообщения и нужный callback. Токен от другого варианта приложения, отказ пользователя и legacy background notification — три разные причины похожего внешнего результата.
Короткий тест доказывает только работу выбранного сценария на проверенном устройстве. Для продукта отдельно проверяют переустановку, смену регистрации, маршрутизацию по нажатию и поведение на поддерживаемых версиях Android. Полезный первый результат — не просто увиденная карточка, а понимание, какой путь регистрации, доставки и отображения её создал.

