Переход с FCM HTTP v1 API на AndroidConfigV2

С помощью FCM AndroidConfigV2 вы можете отправлять сообщения на устройства Android, используя упрощенный API и своевременную доставку уведомлений.

В этом руководстве рассказывается, как перенести запросы на отправку с сервера из AndroidConfig в AndroidConfigV2 и как обновить клиенты приложений Android, чтобы вместо устаревшей обработки onMessageReceived использовать новые обратные вызовы клиента версии 2 – onNotificationReceived и onBackgroundSyncReceived.

Что нового в AndroidConfigV2

API FCM AndroidConfigV2 упрощает работу разработчиков, предлагая два типа сообщений:

  • RemoteNotification – рекомендуется для всех сообщений FCM, которые показывают срочное, ограниченное по времени уведомление, видимое пользователю. Такие сообщения доставляются без задержек, даже если включен режим энергосбережения. На устройстве SDK FCM и сервисы Google Play отвечают за публикацию уведомления в NotificationManager Android.
  • BackgroundSync: несрочные сообщения с данными, предназначенные для фоновой синхронизации и обновления статуса приложения. Для экономии заряда батареи уведомления могут отправляться пакетами или с задержкой.
Функция Устаревшая версия (AndroidConfig v1) Новые (AndroidConfigV2)
Поле конфигурации message.android message.android_v2
Определение приоритета Явное указание priority: "high" или priority: "normal" Подразумевается типом объединения (remote_notification для срочных оповещений и background_sync для несрочной синхронизации)
Публикация уведомлений Приложение вручную вызывает NotificationManager.notify() FCM SDK и GMS Core публикуют напрямую или с помощью NotificationResponse
Обратные вызовы клиента • onMessageReceived(RemoteMessage)
• onDeletedMessages()
• onNotificationReceived(RemoteMessage)
• onBackgroundSyncReceived(RemoteMessage)
• onDeletedMessages()
Сворачиваемость Разные; требуется явная настройка Оба типа сообщений могут быть сворачиваемыми или несворачиваемыми (по умолчанию – несворачиваемые).

Авторизация запросов на отправку AndroidConfigV2

Вы можете по-прежнему использовать токены доступа OAuth 2.0, полученные из сервисного аккаунта Firebase с помощью Application Default Credentials или Admin SDK, и отправлять запросы POST на конечную точку HTTP v1 messages:send.

Если вы не используете сервисный аккаунт Firebase, назначьте своему аккаунту роль Firebase Cloud Messaging API Admin (roles/firebase.messagingAdmin) и запросите область действия OAuth 2.0 https://www.googleapis.com/auth/firebase.messaging при создании токенов доступа.

POST https://fcm.googleapis.com/v1/projects/YOUR_PROJECT_ID/messages:send HTTP/1.1
Authorization: Bearer YOUR_OAUTH_ACCESS_TOKEN
Content-Type: application/json

Как обновить запросы на отправку на сервере (REST)

Чтобы отправить запрос AndroidConfigV2 с помощью REST API, замените объект android в теле запроса на android_v2. В объекте android_v2 укажите общие параметры (например, ttl, collapse_key, data и restricted_package_name) и добавьте одно из следующих полей объединения: remote_notification или background_sync.

Отправка сообщения RemoteNotification

Чтобы отправить срочное уведомление для пользователя, добавьте объект remote_notification и рекомендуемую полезную нагрузку notification. По умолчанию для mutable_content задано значение false. GMS Core сразу же отрисовывает и публикует уведомление в области уведомлений, не выполняя фоновый код приложения.

Полный пример JSON-кода HTTP-запроса (автоматически отрисованное уведомление)

POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA

{
  "message": {
    "token": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "notification": {
      "title": "FCM Message",
      "body": "This is an FCM notification message!"
    },
    "android_v2": {
      "ttl": "3600s",
      "remote_notification": {
        "mutable_content": false,
        "notification": {
          "title": "Order update",
          "body": "Your order has been shipped!",
          "icon": "ic_notification_custom",
          "sound": "default",
          "tag": "alert_tag_1",
          "id": 100
        }
      }
    }
  }
}

Отправьте RemoteNotification с изменяемым контентом.

Если вашему приложению нужно изменить уведомление на устройстве до его показа, задайте mutable_content: true внутри remote_notification. Это указывает GMS Core на необходимость привязки к вашему клиентскому приложению и вызова onNotificationReceived(). Это полезно, когда нужно расшифровать полезную нагрузку со сквозным шифрованием, прикрепить аватары пользователей или локализовать строки.

Обязательно включите объект notification в remote_notification, даже если mutable_content имеет значение true. GMS Core использует этот объект notification в качестве резервного уведомления, если время ожидания обратного вызова клиента истекло или превышены ограничения на подавление.

Если вы переходите с устаревших сообщений с данными версии 1 и хотите, чтобы FCM доставлял полезную нагрузку в старые клиентские установки версии 1 в виде чистого сообщения с данными, установите use_as_v1_data_message: true внутри remote_notification.

Полный пример HTTP-запроса в формате JSON

POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA

{
  "message": {
    "token": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "data": {
      "encrypted_content": "U2FsdGVkX19vN2m+",
      "sender_id": "user_98124"
    },
    "android_v2": {
      "ttl": "3600s",
      "remote_notification": {
        "mutable_content": true,
        "use_as_v1_data_message": true,
        "notification": {
          "title": "New message",
          "body": "You have a new message",
          "tag": "chat_alert",
          "id": 1001
        }
      }
    }
  }
}

Отправить сообщение BackgroundSync

Чтобы отправить несрочное сообщение для фоновой синхронизации данных, обновления состояния без уведомления, обновления значка приложения или виджета, используйте объект background_sync. Не используйте такие сообщения для публикации уведомлений, видимых пользователю, поскольку они могут быть объединены в пакеты или отложены для оптимизации времени работы устройства от батареи.

Полный пример HTTP-запроса в формате JSON

POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA

{
  "message": {
    "token": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "data": {
      "score": "FETCH_UPDATES",
      "sync_action": "FETCH_UPDATES"
    },
    "android_v2": {
      "ttl": "86400s",
      "collapse_key": "app_state_sync",
      "background_sync": {}
    }
  }
}

Обновление обратных вызовов клиента Android

Чтобы получать сообщения AndroidConfigV2 на устройствах Android, обновите приложение, чтобы использовать последнюю версию SDK firebase-messaging. Обновите сервис, расширяющий FirebaseMessagingService, реализовав onNotificationReceived() и onBackgroundSyncReceived().

Установите SDK для программы раннего доступа

В период действия программы раннего доступа SDK firebase-messaging версии 2 распространяется как локальный архив репозитория Maven, а не через общедоступный репозиторий Google Maven.

  1. Добавьте каталог m2repository, содержащий firebase-messaging:26.1.0-eap01, в корневой каталог проекта для Android (<project-root>/m2repository) одним из следующих способов:

    1. Вариант А. Скачайте готовый архив. Скачайте файл FirebaseMessaging_26.1.0-eap01.zip и распакуйте каталог m2repository в корневой каталог проекта Android (<project-root>/m2repository).
    2. Вариант Б. Сборка из GitHub с помощью Gradle. Клонируйте ветку feature/fcm-android-config-v2-eap из firebase/firebase-android-sdk, создайте локальный репозиторий Maven и скопируйте сгенерированный каталог build/m2repository в корневой каталог проекта Android.

      git clone --branch feature/fcm-android-config-v2-eap --single-branch \
        https://github.com/firebase/firebase-android-sdk.git
      cd firebase-android-sdk
      ./gradlew -PprojectsToPublish="firebase-messaging" firebasePublish
      cp -R build/m2repository /path/to/your/android-project/m2repository
      
  2. В файле settings.gradle.kts (или settings.gradle) на уровне проекта добавьте локальный каталог m2repository в начало блока repositories над строкой google():

    Kotlin

    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            maven { url = uri("${rootDir}/m2repository") }
            google()
            mavenCentral()
        }
    }
    

    Groovy

    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            maven { url "${rootDir}/m2repository" }
            google()
            mavenCentral()
        }
    }
    
  3. В файле app/build.gradle.kts (или app/build.gradle) на уровне модуля обновите зависимость firebase-messaging до 26.1.0-eap01:

    Kotlin

    dependencies {
        implementation("com.google.firebase:firebase-messaging:26.1.0-eap01")
    }
    

    Groovy

    dependencies {
        implementation 'com.google.firebase:firebase-messaging:26.1.0-eap01'
    }
    
  4. Синхронизируйте проект с файлами Gradle.

Обработка RemoteNotifications с помощью onNotificationReceived

Если задано значение AndroidConfigV2, сообщения RemoteNotification показываются, если для mutable_content не задано значение True или они не отключены в onNotificationReceived().

Когда поступает RemoteNotification с mutable_content: true, GMS Core связывается с вашим приложением и вызывает onNotificationReceived(). Ваш метод должен возвращать NotificationResponse в течение 20 секунд.

Как вернуть измененное уведомление

Объект Notification, который вы передаете в NotificationResponse.create, заменяет свойства резервной полезной нагрузки remote_notification.notification сервера, определенные в запросе на отправку. Чтобы сохранить значения из запроса на отправку, прочитайте их из message.notification (message.getNotification() в Java), применяя изменения на устройстве:

Kotlin

import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.NotificationResponse
import com.google.firebase.messaging.RemoteMessage

class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onNotificationReceived(message: RemoteMessage): NotificationResponse {
        val serverNotif = message.notification
        val channelId = serverNotif?.channelId ?: "default_channel"
        // Keep the server-defined title unchanged, or fall back to default
        val title = serverNotif?.title ?: "New message"
        // Modify the notification body on-device (for example, decrypting data)
        val decryptedBody = decryptPayload(message.data["encrypted_content"])

        val builder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ic_stat_notification)
            .setContentTitle(title)
            .setContentText(decryptedBody)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setAutoCancel(true)

        // Return the response for the open-source FCM SDK to post to the Android platform
        return NotificationResponse.create("optional_tag", 100, builder.build())
    }
}

Java

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.google.firebase.messaging.NotificationResponse;
import androidx.core.app.NotificationCompat;

public class MyFirebaseMessagingService extends FirebaseMessagingService {

    @Override
    public NotificationResponse onNotificationReceived(RemoteMessage message) {
        RemoteMessage.Notification serverNotif = message.getNotification();
        String channelId = (serverNotif != null && serverNotif.getChannelId() != null)
                ? serverNotif.getChannelId()
                : "default_channel";
        // Keep the server-defined title unchanged, or fall back to default
        String title = (serverNotif != null && serverNotif.getTitle() != null)
                ? serverNotif.getTitle()
                : "New message";
        // Modify the notification body on-device (for example, decrypting data)
        String decryptedBody = decryptPayload(message.getData().get("encrypted_content"));

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
                .setSmallIcon(R.drawable.ic_stat_notification)
                .setContentTitle(title)
                .setContentText(decryptedBody)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setAutoCancel(true);

        // Return the response for the open-source FCM SDK to post to the Android platform
        return NotificationResponse.create("optional_tag", 100, builder.build());
    }
}

Как отключить уведомление

Если у пользователя сейчас открыт нужный экран или чат, вы можете отключить уведомление, вернув NotificationResponse.suppress():

Kotlin

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner

override fun onNotificationReceived(message: RemoteMessage): NotificationResponse {
    val chatId = message.data["chat_id"]
    val isAppForeground = ProcessLifecycleOwner.get()
        .lifecycle
        .currentState
        .isAtLeast(Lifecycle.State.STARTED)

    if (isAppForeground && chatRepository.isConversationActive(chatId)) {
        // Suppress notification display because the user is actively viewing this chat
        return NotificationResponse.suppress()
    }

    val builder = buildNotificationForChat(message)
    return NotificationResponse.create("chat_tag", 101, builder.build())
}

Java

import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.ProcessLifecycleOwner;

@Override
public NotificationResponse onNotificationReceived(RemoteMessage message) {
    String chatId = message.getData().get("chat_id");
    boolean isAppForeground = ProcessLifecycleOwner.get()
            .getLifecycle()
            .getCurrentState()
            .isAtLeast(Lifecycle.State.STARTED);

    if (isAppForeground && chatRepository.isConversationActive(chatId)) {
        // Suppress notification display because the user is actively viewing this chat
        return NotificationResponse.suppress();
    }

    NotificationCompat.Builder builder = buildNotificationForChat(message);
    return NotificationResponse.create("chat_tag", 101, builder.build());
}

Уведомления о трансляциях и отмена старых оповещений (FcmNotificationManager)

Если вам нужно опубликовать несколько уведомлений для одного входящего push-уведомления или отменить устаревшее уведомление перед показом нового, используйте FcmNotificationManager:

Kotlin

override fun onNotificationReceived(message: RemoteMessage): NotificationResponse {
    // Retrieve the scoped manager for this message execution
    val manager = getFcmNotificationManager(message)

    // 1. Cancel an outdated notification right away
    manager.cancelNow("message_tag", 50)

    // 2. Post the item notification immediately
    val itemNotif = NotificationCompat.Builder(this, "chat_channel")
        .setSmallIcon(R.drawable.ic_message)
        .setContentTitle("Alex")
        .setContentText(message.data["text"])
    manager.postNow("item_tag", 51, itemNotif.build())

    // 3. Post a group summary notification
    val summaryNotif = NotificationCompat.Builder(this, "chat_channel")
        .setSmallIcon(R.drawable.ic_message)
        .setContentTitle("New Conversations")
        .setGroupSummary(true)
    manager.postNow("summary_tag", 52, summaryNotif.build())

    // 4. Return finished() to complete the streaming workflow
    return manager.finished()
}

Java

@Override
public NotificationResponse onNotificationReceived(RemoteMessage message) {
    // Retrieve the scoped manager for this message execution
    FcmNotificationManager manager = getFcmNotificationManager(message);

    // 1. Cancel an outdated notification right away
    manager.cancelNow("message_tag", 50);

    // 2. Post the item notification immediately
    NotificationCompat.Builder itemNotif = new NotificationCompat.Builder(this, "chat_channel")
            .setSmallIcon(R.drawable.ic_message)
            .setContentTitle("Alex")
            .setContentText(message.getData().get("text"));
    manager.postNow("item_tag", 51, itemNotif.build());

    // 3. Post a group summary notification
    NotificationCompat.Builder summaryNotif = new NotificationCompat.Builder(this, "chat_channel")
            .setSmallIcon(R.drawable.ic_message)
            .setContentTitle("New Conversations")
            .setGroupSummary(true);
    manager.postNow("summary_tag", 52, summaryNotif.build());

    // 4. Return finished() to complete the streaming workflow
    return manager.finished();
}

Обработка сообщений BackgroundSync с помощью onBackgroundSyncReceived

Чтобы обрабатывать сообщения BackgroundSync на Android, используйте onBackgroundSyncReceived():

Kotlin

override fun onBackgroundSyncReceived(message: RemoteMessage) {
    val syncCommand = message.data["sync_action"]

    try {
        // Perform local database synchronization or cache refresh
        AppDataRepository.getInstance(applicationContext).syncWithServer(syncCommand)
    } catch (e: Exception) {
        Log.e("FCM", "Background sync operation failed", e)
    }
    // Execution completes -> GMS Core marks the message as handled and unbinds.
}

Java

@Override
public void onBackgroundSyncReceived(RemoteMessage message) {
    String syncCommand = message.getData().get("sync_action");

    try {
        // Perform local database synchronization or cache refresh
        AppDataRepository.getInstance(getApplicationContext()).syncWithServer(syncCommand);
    } catch (Exception e) {
        Log.e("FCM", "Background sync operation failed", e);
    }
    // Execution completes -> GMS Core marks the message as handled and unbinds.
}

Обработка ожидающих удаления сообщений с помощью onDeletedMessages

В некоторых случаях, например если устройство не подключалось к FCM в течение длительного времени или очередь сервера заполнена, FCM может удалить ожидающие сообщения до доставки. В таких случаях переопределите onDeletedMessages():

Kotlin

override fun onDeletedMessages() {
    // Perform a full state synchronization with your app server
    AppDataRepository.getInstance(applicationContext).scheduleFullSync()
}

Java

@Override
public void onDeletedMessages() {
    // Perform a full state synchronization with your app server
    AppDataRepository.getInstance(getApplicationContext()).scheduleFullSync();
}

Как перенести данные определенных приложений

Доставка уведомлений со сквозным шифрованием

При доставке уведомления со сквозным шифрованием с помощью RemoteNotification расшифрованный контент уведомления остается в приложении и на платформе Android.

  1. Доставка изменяемого уведомления. Ваш сервер отправляет RemoteNotification с mutable_content: true, зашифрованную полезную нагрузку в data и общую незашифрованную резервную копию notification. GMS Core передает зашифрованное сообщение в SDK FCM с открытым исходным кодом, который находится в вашем приложении.
  2. Расшифровка и возврат уведомления. SDK FCM вызывает onNotificationReceived(). Приложение расшифровывает полезную нагрузку локально, создает окончательный расшифрованный объект Notification для Android и передает его обратно в SDK FCM с открытым исходным кодом, используя NotificationResponse или FcmNotificationManager.
  3. Публикация на платформе и передача метаданных. SDK FCM с открытым исходным кодом публикует расшифрованное уведомление напрямую в API NotificationManager платформы Android и отправляет в GMS Core только метаданные уведомления, например идентификаторы и временные метки, чтобы можно было отслеживать доставку. Расшифрованный контент никогда не отправляется в GMS Core.
  4. Показ резервного уведомления (при необходимости). Если приложение не отвечает в течение 20 секунд или превышает ограничения на подавление фоновых уведомлений, GMS Core отправляет стандартное резервное уведомление notification напрямую на платформу Android.

Схема архитектуры, на которой показан четырехэтапный процесс доставки уведомлений со сквозным шифрованием между GMS Core, FCM SDK с открытым исходным кодом, вашим приложением и NotificationManager API платформы Android.

Как работает подавление уведомлений и тайм-ауты

Чтобы обеспечить надежную доставку в экосистеме Android, GMS Core применяет тайм-ауты обработки и ограничения на подавление уведомлений при обработке сообщений RemoteNotification:

Время обработки истекло

Если для параметра mutable_content задано значение true, метод onNotificationReceived() должен возвращать NotificationResponse в течение 20 секунд. Если выполнение превышает это пороговое значение, обратный вызов отменяется, а GMS Core автоматически публикует резервное уведомление, созданное на основе полезной нагрузки remote_notification.notification.

Доля заблокированных уведомлений

Поскольку сообщения RemoteNotification обходят ограничения спящего режима, чтобы немедленно оповещать пользователей, и доставляются без задержки, приложения должны показывать уведомления о полученных сообщениях RemoteNotification.

Каждому приложению разрешено подавлять до 25% трафика RemoteNotification при работе в фоновом режиме. Если доля подавленных уведомлений превышает 25%, GMS Core автоматически создает и публикует резервное уведомление на основе полезной нагрузки remote_notification.notification.

Блокировки, выполненные, когда приложение активно на переднем плане, не учитываются. Приложения, которым предоставлены разрешения на неограниченное использование в фоновом режиме, также освобождаются от ограничений на подавление фонового режима.