Рекомендации по управлению регистрацией в FCM

Если вы используете API FCM для программного создания запросов на отправку, то со временем можете обнаружить, что тратите ресурсы на отправку сообщений на неактивные устройства с устаревшими регистрациями. Это может повлиять на данные о доставке сообщений, которые отображаются в консоли Firebase или экспортируются в BigQuery. В результате может показаться, что доля доставленных сообщений резко снизилась, хотя на самом деле это не так. В этом руководстве рассказывается о том, как обеспечить эффективный таргетинг сообщений и корректную отчетность по их доставке.

Устаревшие и просроченные регистрации

Устаревшие регистрации связаны с неактивными устройствами, которые не подключались к FCM более месяца. Со временем вероятность того, что устройство снова подключится к FCM, будет снижаться. Сообщения, отправленные на такие устройства, скорее всего, не будут доставлены.

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

Если регистрация на устройстве Android неактивна в течение 270 дней, FCM считает ее устаревшей и удаляет. Когда срок регистрации истекает, FCM помечает ее как недействительную и отклоняет отправку на нее. Обратите внимание, что идентификаторы установки Firebase (FID) управляются сервисом установки Firebase (FIS), а не FCM. В редких случаях, когда устройство снова подключается и приложение открывается после того, как его регистрация была удалена, клиентское приложение снова регистрируется в FCM, используя FID, полученный от FIS. Обратите внимание, что FID может измениться. Подробнее о том, когда FID перевыпускаются, читайте в статье Управление установками Firebase.

На других платформах, например iOS, FCM использует базовый сервис push-уведомлений (например, APNs), в котором нет такого же 270-дневного срока действия, основанного на неактивности. Мы рекомендуем поддерживать актуальность регистраций и удалять устаревшие.

Основные рекомендации

При создании приложений, которые используют API FCM для программной отправки запросов, необходимо следовать некоторым основным рекомендациям. Основные рекомендации:

  • Получите идентификаторы установок Firebase (FID) из FCM и сохраните их на сервере приложения. Важная роль сервера – отслеживать зарегистрированные идентификаторы FID каждого клиента и поддерживать актуальный список активных идентификаторов FID. Мы настоятельно рекомендуем добавить в базу данных временную метку регистрации и обновлять ее при каждой загрузке данных о регистрации.
  • Поддерживайте актуальность регистраций и удаляйте устаревшие. Помимо удаления регистраций, которые FCM больше не считает действительными, вы можете отслеживать другие признаки того, что регистрации устарели, и удалять их заранее. В этом руководстве рассказывается о том, как это сделать.

Как получить и сохранить идентификаторы установок Firebase

При первом запуске приложения FCM SDK регистрирует экземпляр приложения в FCM и возвращает идентификатор установки Firebase (FID). Этот идентификатор необходимо включать в запросы на отправку таргетированных сообщений из API или использовать для подписок на темы.

Мы настоятельно рекомендуем сохранять FID на сервере приложения вместе с временной меткой при каждой загрузке. Обновляя временную метку при каждом запросе на загрузку, сервер узнает, когда экземпляр приложения был открыт в последний раз и успешно синхронизирован с серверной частью FCM.

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

  • Если включена автоматическая инициализация(рекомендуется), SDK автоматически обновляет регистрацию и отслеживает изменения. Обратный вызов onRegistered() регулярно выполняется при плановой синхронизации во время запуска приложения, а также при изменении FID. Просто реализуйте этот обратный вызов, чтобы загрузить FID на сервер и сохранить текущую временную метку.
  • Если автоматическая инициализация отключена, обратный вызов onRegistered() не будет автоматически выполняться при запуске. Чтобы отслеживать регистрации и обновлять их, вызывайте register() при запуске приложения, например в onCreate() основного действия на Android. При успешном звонке запускается процесс регистрации FCM с использованием FID, который передается в обратный вызов onRegistered(). Это позволяет приложению загрузить FID и обновить временную метку на сервере.

Пример: хранение идентификаторов FID и временных меток в Cloud Firestore

Например, вы можете использовать Cloud Firestore, чтобы хранить идентификаторы экземпляров в коллекции fcmRegistrations. Каждый идентификатор документа в коллекции соответствует идентификатору пользователя, а в документе хранится текущий FID и временная метка его последнего обновления. Используйте функцию set, как показано в этом примере на Kotlin:

private fun sendRegistrationToServer(installationId: String?) {
    // If you're running your own server, call API to send registration details and today's date for the user

    // Example shown uses Firestore
    // Add FID and timestamp to Firestore for this user
    val deviceFid = hashMapOf(
        "installationId" to installationId,
        "timestamp" to FieldValue.serverTimestamp(),
    )
    // Get user ID from Firebase Auth or your own server
    Firebase.firestore.collection("fcmRegistrations").document("myuserid")
        .set(deviceFid)
}

При каждой успешной регистрации или обновлении идентификатора установки Firebase вызывается обратный вызов onRegistered(). Чтобы загрузить FID и обновить временную метку, реализуйте этот обратный вызов:

override fun onRegistered(installationId: String) {
    Log.d(TAG, "Registered installation ID: $installationId")

    // Send the Firebase Installation ID (FID) to your app server. Your app
    // server should save the FID and update the timestamp upon receipt.
    sendRegistrationToServer(installationId)
}

Если автоматическая инициализация отключена, вызовите register() при запуске приложения (например, в onCreate()), чтобы запустить процесс регистрации и доставки FID через onRegistered():

// Trigger manual registration if auto-initialization is turned off.
FirebaseMessaging.getInstance().register()
    .addOnCompleteListener(this) { task ->
        if (task.isSuccessful) {
            // The registration callback onRegistered() will be invoked with the current FID.
        } else {
            Log.w(TAG, "Failed to register with Firebase Cloud Messaging", task.exception)
        }
    }

Поддерживайте актуальность регистраций и удаляйте устаревшие

Определить, является ли регистрация свежей или устаревшей, не всегда просто. Чтобы охватить все случаи, необходимо установить пороговое значение, по достижении которого регистрация будет считаться устаревшей. По умолчанию FCM считает регистрацию устаревшей, если экземпляр приложения не подключался в течение месяца. Если регистрация была выполнена более месяца назад, устройство, скорее всего, неактивно. В противном случае регистрация была бы обновлена.

В зависимости от ситуации один месяц может быть слишком коротким или слишком длинным периодом, поэтому вы можете выбрать другие критерии.

Обнаружение недопустимых ответов от серверной части FCM

Убедитесь, что вы выявляете недопустимые ответы от FCM и удаляете из своей системы все регистрации, которые считаются недопустимыми или срок действия которых истек. При использовании HTTP v1 API эти сообщения об ошибках могут указывать на то, что ваш запрос на отправку был нацелен на недействительные или просроченные регистрации:

  • UNREGISTERED (HTTP 404)
  • INVALID_ARGUMENT (HTTP 400)

Если вы уверены, что полезная нагрузка сообщения действительна, и получаете один из этих ответов для целевой регистрации, можете удалить запись об этой регистрации, поскольку она больше не будет действительна. Например, чтобы удалить недействительные регистрации из Cloud Firestore, можно развернуть и запустить функцию, подобную следующей:

        // Firebase Installation ID comes from the client FCM SDKs
        const firebaseInstallationId = 'YOUR_FIREBASE_INSTALLATION_ID';

        const message = {
            data: {
                // Information you want to send inside of notification
            },
            fid: firebaseInstallationId
        };

        // Send message to device with provided Firebase Installation ID
        getMessaging().send(message)
        .then((response) => {
            // Response is a message ID string.
        })
        .catch((error) => {
            // Delete registration for user if error code is UNREGISTERED or INVALID_ARGUMENT.
            if (error.errorCode == "messaging/registration-token-not-registered") {
                // If you're running your own server, call API to delete the registration for the user
                // Example shown uses Firestore
                // Get user ID from Firebase Auth or your own server
                Firebase.firestore.collection("fcmRegistrations").document(user.uid).delete()
            }
        });

FCM возвращает недопустимый ответ, если регистрация устройства Android истекла после 270 дней неактивности или если клиент явно отменил регистрацию. Если вам нужно более точно отслеживать устаревшие регистрации в соответствии с вашими собственными определениями, вы можете упреждающе удалять устаревшие регистрации.

Регулярно обновляйте регистрации

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

Если клиентское приложение использует API FID, вам не нужно планировать периодические фоновые задачи в клиентском приложении для получения или обновления регистраций. SDK автоматически обновляет FID при автоинициализации, регулярно передавая правильный текущий FID в обратный вызов onRegistered() при обычной синхронизации во время запуска приложения.

Чтобы на сервере всегда были актуальные данные, реализуйте стратегии загрузки при запуске, описанные в разделе Получение и хранение идентификаторов установок Firebase.

  • Автоинициализация включена. SDK автоматически обеспечивает отправку на ваш сервер последнего FID при регулярной синхронизации во время запуска приложения.
  • Автоматическая инициализация отключена или не поддерживается. Вызовите register() при запуске приложения (например, в Android – в основном действии onCreate()), чтобы принудительно запустить последовательность регистрации и активировать доставку FID в обратный вызов onRegistered().

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

Устаревшие API токенов регистрации

Если вы используете устаревшие токены регистрации, клиентский SDK не будет автоматически обновлять их при обычной синхронизации. Поэтому мы рекомендуем периодически получать и обновлять все токены регистрации на сервере. Для этого вам нужно:

  • Добавьте в клиентское приложение логику, которая будет получать текущий токен с помощью вызова API (например, token(completion): для платформ Apple или getToken() для Android) и отправлять его на сервер приложения для хранения (с временной меткой). Это может быть ежемесячное задание, настроенное для всех клиентов или токенов.
  • Добавьте логику сервера, чтобы регулярно обновлять временную метку токена, независимо от того, изменился он или нет.

Пример логики для Android, позволяющей обновлять устаревшие токены с помощью WorkManager, можно найти в статье Управление токенами Cloud Messaging в блоге Firebase.

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

Удаление устаревших регистраций

Перед отправкой сообщений на устройство убедитесь, что временная метка регистрации устройства находится в пределах допустимого периода устаревания. Например, вы можете реализовать Cloud Functions for Firebase, чтобы ежедневно проверять, находится ли временная метка в пределах определенного периода устаревания, например const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 30;, а затем удалять устаревшие регистрации:

exports.pruneRegistrations = functions.pubsub.schedule('every 24 hours').onRun(async (context) => {
  // Get all documents where the timestamp exceeds is not within the past month
  const staleRegistrationsResult = await admin.firestore().collection('fcmRegistrations')
      .where("timestamp", "<", Date.now() - EXPIRATION_TIME)
      .get();
  // Delete devices with stale registrations
  staleRegistrationsResult.forEach(function(doc) { doc.ref.delete(); });
});
exports.pruneTokens = functions.pubsub.schedule('every 24 hours').onRun(async (context) => { // Получить все документы, временная метка которых не относится к последнему месяцу const staleTokensResult = await admin.firestore().collection('fcmTokens') .where("timestamp", "<", Date.now() - EXPIRATION_TIME) .get(); // Удалить устройства с устаревшими токенами staleTokensResult.forEach(function(doc) { doc.ref.delete(); }); });

Отмена подписки на темы для устаревших регистраций

Если вы используете темы, то можете отменить подписку устаревших регистраций на темы, на которые они подписаны. Для этого нужно выполнить два действия:

  1. Приложение должно повторно подписываться на темы каждый раз, когда меняется идентификатор установки Firebase (FID). Это позволяет автоматически восстанавливать подписки, когда приложение снова становится активным.
  2. Если экземпляр приложения неактивен в течение месяца (или другого заданного вами периода), отмените его подписку на темы с помощью Firebase Admin SDK, чтобы удалить сопоставление идентификатора установки Firebase с темой из серверной части FCM.

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

Оценка успешности доставки

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

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

  • Указывают ли данные Google Аналитики, BigQuery или других систем отслеживания на то, что регистрация активна?
  • Были ли предыдущие попытки доставки неудачными в течение определенного периода времени?
  • Обновлялся ли идентификатор установки Firebase на ваших серверах за последний месяц?
  • Для устройств Android FCM Data API сообщает о высоком проценте сбоев доставки сообщений из-за droppedDeviceInactive?

Подробнее о доставке сообщений…