Пуш-уведомления RuStore
Привет! Меня зовут Артем Ковардин, я работаю в VK и мы с командой разрабатываем Push Service RuStore и SDK для него. Если вы занимаетесь разработкой мобильных приложений и хотели бы иметь канал коммуникации с пользователями или же вам просто интересно то, как работают разработанные нами пуш-уведомления и как их можно масштабировать и интегрировать, то статья ниже — для вас.
Зачем вообще нужен сервис пуш-уведомлений
Если приложение не подразумевает обратной связи с пользователями, то, в целом, можно обойтись и без пушей. Но если приложение »коммуницирует» с пользователем: уведомляет об изменениях баланса и проведённых операциях по счету, или же внутри игры встроены различные ивенты по таймеру, а может — подъезжает заказ или курьер, то, чтобы не рассылать дорогостоящие СМС, можно воспользоваться пуш-уведомлениями.
Мы сделали свой транспорт для отправки пушей на приложения Android. Он передаёт пуш-уведомления от сервера к конечным мобильным приложениям. Это альтернатива сервису Firebase Cloud Messaging (FCM) от Google. И, что очень важно, собственный транспорт помогает сохранять данные пользователей в безопасности.
Как устроены пуш-уведомления RuStore
Архитектура
Дистрибьюторы
На мобильном устройстве пользователя работает дистрибьютор сообщений, который передаёт пуш-уведомление в конечное приложение. Для резервирования на девайсе может быть несколько дистрибьюторов. Для приложений из RuStore сам магазин и является дистрибьютором по умолчанию. В качестве резервных дистрибьюторов используются другие приложения VK.
Авторизация
Чтобы получать пуш-уведомления, конечному пользователю надо пройти авторизацию на дистрибьюторе. Она нужна для получения пуш-токена, который хранится на пуш-сервере. Авторизации позволяет избежать генерации большого количества пуш-токенов, что может привести к неправильной работе сервиса.
Отправка пуш-уведомления
После запроса на отправку пуш-уведомления, сервер передаёт пуш приложению RuStore, а RuStore отправляет его на SDK конечного приложения. Затем системные функции приложения показывают пуш-уведомление пользователю.
Соединение с сервером
Сетевое соединение с сервером поддерживает не каждое конечное приложение, а дистрибьютор, которым выступает RuStore или другое приложение, в котором встроена логика дистрибуции пуш-уведомлений. Все пуш-уведомления передаются через него. Это помогает снизить расход батареи.
Мы стараемся держать процесс RuStore в фоновом режиме постоянно, но на некоторых устройствах система рано или поздно отключает приложение RuStore, даже если не получено разрешение на отключение оптимизации энергопотребления. На такой случай мы предусмотрели механизмы повторного запуска. Например, мы пингуем по AIDL (Android Interface Definition Language) хостовое приложение, а также запускаем воркеры для получения пушей.
Безопасность
Для защиты отправки пуш-уведомлений мы принимаем следующие меры:
Проверяем отпечатки подписей всех приложений — и клиентских, и дистрибьюторов. Если отпечатки не совпадают, приложение не получает пуш-уведомление.
Список актуальных дистрибьюторов хранится на сервере.
Если на устройстве установлено несколько дистрибьюторов, главный из них выбирается на сервере. Для этого мы собираем информацию обо всех доступных дистрибьюторах и отправляем её на сервер, где срабатывает алгоритм выбора оптимального дистрибьютора.
Universal Push
Добавление нового канала отправки пуш-уведомлений — это дополнительная работа для разработчика. Чтобы упростить процесс, мы разработали универсальный SDK для пуш-уведомлений, который позволяет использовать и собственный транспорт, и другие доступные на сегодня варианты. На российском рынке собственный транспорт пока есть только у RuStore.
Universal Push SDK даёт возможность вариативно использовать каналы отправки пуш-уведомлений в зависимости от платформ распространения или же их доступности: FCM, HMS, RuStore, а также тех, которые появятся в будущем. Сейчас Universal Push SDK состоит из следующих пакетов:
universal-push
— общий интерфейс.push-fcm
— транспорт Firebase.push-hms
— транспорт Huawei.push-rustore
— транспорт RuStore.
Первый пакет — общая реализация, три последние — конкретные реализации. В приложение можно подключить только те пакеты, которые нужны.
Подключение Universal Push
dependencies {
implementation 'ru.rustore.sdk:univeraslpush'
implementation 'ru.rustore.sdk:univeraslrustore'
implementation 'ru.rustore.sdk:univeraslhms'
implementation 'ru.rustore.sdk:univeraslfcm'
}
Пример инициализации Universal Push
RuStoreUniversalPushClient.init(
context = this,
rustore = RuStorePushProvider(
application = this,
projectId = "m3Id6aPeXq36mpNb5q000IodEDHlGhL0",
logger = DefaultLogger(tag = tag),
),
firebase = FirebasePushProvider(
context = this,
),
hms = HmsPushProvider(
context = this,
appid = "108003365",
),
)
Пример использования Universal Push
RuStoreUniversalPushClient.getTokens()
.addOnCompleteListener(object : OnCompleteListener
Отправка по всем каналам
С Universal Push можно отправлять уведомления через несколько каналов одновременно. Это увеличивает вероятность успешной доставки. Когда пуш успешно доставляется по нескольким каналам, Universal Push SDK убирает дубликаты, чтобы пользователь не получал одно и то же сообщение несколько раз. Для этого нужно указывать идентификатор пуш-уведомлния.
Подключение только канала RuStore
Universal Push также можно использовать, если у вас уже есть своя реализация работы с FCM пушами:
RuStoreUniversalPushManager.isUniversalPushMessage()
RuStoreUniversalPushManager.processToken()
RuStoreUniversalPushManager.processMessage()
Обычно разработчики выбирают такой способ:
Если для отправки пуш-уведомлений уже используется FCM или HMS, и нет необходимости отказываться от существующей схемы.
Если в приложение нельзя встраивать FCM.
Пуши RuStore в сервисе от Edna
Всё, что мы обсудили ранее, касается ситуации, когда разработчик интегрирует SDK пуш-уведомлений напрямую через RuStore. Однако наш канал для отправки пушей доступен в других сервисах. Одним их них является сервис от Edna.
Компания, помогала RuStore c разработкой пуш-облака в качестве эксперта в решениях и сервисах для цифровых коммуникаций. А уже в октябре 2022 они интегрировали этот канал к себе и предлагают разработчикам отправку пуш-уведомлений в том числе и через RuStore как часть своего сервиса. Разработчики интегрируют их пуш-библиотеки в собственные приложения, после чего получают возможность отправлять пуши сразу во все облака (FCM, HMS, APNS и RuStore) через единый интерфейс.
Для разработчиков это:
Единая точка входа в сервис отправки уведомлений (разных, а не только пушей).
Каскадирование и омниканальность: можно настроить логику отправки каждого типа сообщений по разным каналам. Например, если пуш не доставлен, то запускается отправка в WhatsApp, а только затем СМС.
Помощь с настройкой и поддержкой уведомлений, если нет желания разбираться самостоятельно.
Для RuStore это опыт интеграции нашего SDK в сторонний сервис, возможность оперативно дорабатывать свой функционал и собирать обратную связь с экспертов в области.
Схема отправки пуша
В полученном решении отправка пушей от Edna выполняется сразу по всем доступным каналам, которые подключены для клиентского приложения. Сервис отправляет пуш-уведомление, когда получены:
Инициализация
При запуске клиентского приложения происходит инициализация, в процессе которой библиотека отправляет асинхронные запросы, чтобы получить все доступные пуш-токены от FCM, HMS и RuStore. После получения пуш-токенов все адреса отправляются на Backend, где для разработчика формируется единый адрес, по которому можно отправить пуш-уведомление.
Отправка пуш-уведомления
Далее происходит отправка уведомления. На стороне разработчика срабатывает триггер, например, когда конечный пользователь оплачивает покупку банковской картой. Библиотека Edna получает сообщение по единому адресу, извлекает все доступные пуш-токены и одновременно отправляет пуш по адресам всех доступных пуш-облаков, , а облака пытаются доставить пуш-уведомление.
Возможны разные ситуации, например:
Пуш не доставлен ни одним облаком;
Пуш доставлен только одним из транспортов;
Пуш доставлен всеми облаками.
Библиотека показывает только первый пуш, независимо от канала, по которому он пришёл. Второй и третий пришедшие пуши пропускаются и помечаются как дубликаты.
Контроль доставки
Все пуши отслеживаются. При получении пуша библиотека передаёт на Backend сквозной ID пуша. Так сервис понимает, какие облака доставили пуш, а какие нет. Это помогает рассчитывать процент доставки для разных каналов.
Работа с очередями сообщений
Между сервисом и пуш-облаками существует разделение зон ответственности, где сервис формирует сообщение и отправляет его в пуш-облако, а доставку на мобильное устройство выполняет уже само пуш-облако.
Очереди в пуш-облаке
Далее пуш-облако устанавливает соединение с устройством и доставляет пуш. В случае, если соединение не активно или устройство не онлайн, в пуш-облаке может появиться очередь сообщений. Пуш-облако пытается доставить уведомление на устройство в течение времени жизни пуша (TTL). По истечении указанного времени, пуш считается просроченным и исключается из очереди.
Time to live
TTL для пуша обычно бывает в диапазоне от 30 секунд до 2 часов. На практике 95% пушей приходят в течение первых 5 секунд.
И в итоге мы получаем повышенную доставляемость уведомлений за счёт всех доступных транспортов, а повышенная доставляемость позволяет конечному разработчику как улучшить коммуникацию с пользователем, так и значительно сэкономить на платных сообщениях, особенно для критически важных нотификаций.
Планы и ссылки
Совсем недавно мы дали возможность отправки тестовых Push из консоли RuStore, чтобы удобно проверять их работу, и планируем расширить возможности и в Web, это упростит работу с уведомлениями и ускорит часть процессов запуска и тестирования.
Информацию о пуш-уведомлениях можно почитать в документации, а примеры работы с SDK пуш-уведомлений доступны на GitFlic:
Также по ссылкам ниже можно посмотреть записи докладов, на которых мы подробно рассказываем по пуши:
Если у вас есть вопросы по работе Push-уведомлений от RuStore или предложения по доработке, то заходите в наш чатик для разработчиков, где мы собираем обратную связь и отвечаем на вопросы :)