Google Cloud Messaging: «Сова, відкривай! Пуш прийшов!»

Всім відомий сервіс Google Cloud Messaging (GCM) потрібен для того, щоб ваш додаток завжди показувало актуальні дані користувачеві. Схема роботи сервісу включає в себе три компоненти.



Безпосередньо сервер GCM, ваш пуш-сервер і пристрій з встановленим додатком. Алгоритм роботи простий: пристрій реєструється в GCM, отримує registrationId – якийсь маркер, який використовується в подальшому, – зберігає його у себе локально і передає серверу. Далі пуш-сервер використовує цей registrationId для відправки повідомлень вашому додатку на пристрої.

У цьому матеріалі будуть розглянуті проблеми на двох ділянках, що позначені на схемі: пуш-сервер – GCM і GCM – пристрій.

Обробка помилок від сервера GCM
У разі успішної відправки повідомлення ваш пуш-сервер отримає відповідь від GCM зі статус кодом 200 і ненульовий message_id.



Помилки приходять в тілі відповіді зі статус-кодом 200. Тому покладатися лише на статус-код 200 недостатньо. Тут я наведу приклад однієї з найбільш важливих помилок, це NotRegistered, інші менш цікаві. У більшості випадків вона означає, що додаток, яке ваш пуш-сервер відправляє повідомлення було видалено, або додаток вже використовує інший registrationId, а ваш пуш-сервер чомусь про це не знає. Отримавши таку відповідь від GCM, пуш-сервер повинен негайно видалити даний registrationId із свого сховища.
Для моніторингу помилок підключайте GCM статистику в консолі розробника.

Синхронізація registrationId на клієнті і пуш-сервері
RegistrationId є однією з найважливіших частин інфраструктури GCM. Асинхронність registrationId між клієнтом і пуш-сервером призведе до сумних наслідків. Є всі шанси, що користувачі залишаться без пуш-повідомлень навічно. GCM відстежує ситуацію, коли на пристрої з якихось причин оновлюється registrationId, і повідомляє про це пуш-сервера за допомогою параметра canonical_ids.



Кейс з canonical_ids відтворити можна наступним чином:
  1. Встановлюєте додаток
  2. Відправляєте на нього повідомлення
  3. Видаляєте додаток
  4. Встановлюєте додаток
  5. Відправляєте на нього повідомлення
Після відправки повідомлення у кроці 5 вам прийде відповідь від GCM з параметром canonical_ids рівним 1 і безпосередньо свіжий registrationId, який вже щосили використовується вашим клієнтом.



Отримавши таку відповідь, пуш-сервер просто зобов'язаний оновити registrationId значення відповіді. Якщо цього не зробити, то ще якийсь час повідомлення будуть доходити до клієнта і старий registrationId буде валиден, але рано чи пізно GCM відповість помилкою NotRegistered, після чого пуш-сервер видалить registrationId, і користувачі назавжди забудуть про пуш-повідомлення в вашому додатку. Тому обробляйте параметр canonical_ids і не доводьте до гріха.

Два основних підходи в роботі з GCM
Перший — це Messages with Payload. Суть його в тому, щоб в самому повідомленні передавати якусь корисну інформацію. Наприклад, в месенджері це може бути текст повідомлення, у прикладній програмі — сама новина. Другий механізм — це Send to Sync. Він зручніший за витратою трафіку, т. к. в саме повідомлення не упаковується багато даних. Повідомлення виступає в ролі сигналу про те, що додатком слід забрати свіжі дані з сервера. Другий підхід безпосередньо пов'язаний з параметром collapse_key.

Messages with Payload
Ідеальна ситуація, коли ваш пристрій тримає з'єднання з сервером GCM, повідомлення надсилаються і успішно доставляються на пристрій. Якщо з'єднання немає (наприклад, ви застрягли в ліфі або зайшли в метро), а вам в цей час йдуть повідомлення, то вони починають складатися в якусь чергу в GCM-сховище. Ця черга не нескінченна, ліміт становить 100 повідомлень. Як тільки прийде 101 повідомлення, то всі вони віддаляються і більше не накопичуються. Коли пристрій зловить мережу і встановить з'єднання з GCM, в додаток прийде intent з інформацією про те, що було видалено, наприклад, 345 повідомлень.



Отримавши такий намір, потрібно не полінуватися і сходити на сервер за свіжими даними. Інакше користувач побачить їх тільки коли прийде чергове пуш-повідомлення, а коли воно прийде – нікому невідомо. Це дуже важливий момент, про який треба пам'ятати при реалізації підходу «Messages with Payload».

Send to Sync
Припустимо, ми використовуємо collapse_key. Це якась константа, яких може бути не більше чотирьох для одного registrationId, тобто для одного инстанса програми. Наприклад, новинне додаток збирає якісь дані з різних сервісів. Нехай один сервер віддає спортивні новини, інший — культуру, третій — політику, четвертий — авто. Виникне проблема, звичайно, коли з'явиться п'ятий сервіс, але зараз не в цьому суть. У надсилання повідомлення для відповідної рубрики можна використовувати свій collapse_key: sport, culture, policy, auto.

При приході чергового повідомлення з одним і тим же collapse_key GCM замінює старе повідомлення знову прийшли. В принципі, логічно, оскільки ми пам'ятаємо, що повідомлення в підході «Send to Sync» є лише сигналом нашого додатком про те, що слід сходити на сервер за свіжими даними. Але тут нас підстеріг один неприємний момент, з-за якого нам довелося відмовитися від підходу «Send to Sync» — тротлінг. Тротлінг полягає в тому, що GCM сервер може деякий час чекати, щоб зібрати якомога більше повідомлень з однаковим collapse_key. Все б добре, але це вносить затримку в доставку повідомлення до клієнта (стабільно помічав затримку в півхвилини-хвилину), що неприпустимо для деяких типів додатків, наприклад, месенджера.



З-за цієї затримки ми перестали використовувати collapse_key. Якщо у вашому додатку некритична невелика затримка в доставці повідомлень, то підхід «Send to Sync» — хороший вибір.

З часом ми врахували всі вищеописані деталі в імплементації нашого пуш-сервера. Але як і раніше залишалося велика кількість відгуків з приблизно таким змістом: «Я бачу нові повідомлення, тільки коли заходжу в додаток. Коли воно не запущено, до мене повідомлення не доходять!!!». Спочатку основною гіпотезою була розсинхронізація registrationId, що зберігаються на пристрої та пуш-сервері. Для її підтвердження ми закрутили на пристрої перевірку, суть якої в тому, щоб додаток періодично запитувала пуш-сервер: «У тебе є мій registratioinId?». Відповідь «так» гарантує нам з великою часткою ймовірності, що registrationId актуальний.



І згідно зі статистикою, відповідей «так» 99,7%. Що дозволило нам зробити висновок, що з синхронізацією registrationId все нормально. Почали шукати проблему на ділянці між пристроєм і GCM. Неодноразово був свідком ситуації, коли на Samsung S4, хай простить мене Samsung, вимикаєш екран, і повідомлення починають приходити з великою затримкою (порядку 10 – 15 хвилин). З допомогою наших колег мережевих адміністраторів було з'ясовано, що TCP-з'єднання між вашим пристроєм і GCM ставало неактивним (idle), і пакети переставали ходити. Причиною всього цього так званий «heartbeat». «Heartbeat» — це пакетик (ping), посланий системою раз у певний інтервал часу, щоб «оживити» TCP-з'єднання між вашим пристроєм і GCM (почитати більш детально про це можна тут).



І інтервал, через який надсилається heartbeat, досить великий. Начебто, в серпні 2014 року його скоротили до 8 хвилин, але інформація, можливо, неточна. В інтернеті пропонується рішення, яке застосовується в так званих «пуш-фиксирах». Суть його в тому, щоб ініціювати посилку heartbeat-пакету вручну. Але на жаль, це рішення працює тільки для root-пристроїв.



Оптимізму домогтися миттєвої доставки повідомлень на всіх підтримуваних нами пристроях (за винятком китайських айфонів на андроїд) засобами GCM залишалося все менше. А проблему із затримкою доставки повідомлень треба вирішити. Єдине, що може гарантувати більш-менш стабільну по часу доставку повідомлень — це тримати власне з'єднання з сервером. Але для початку хотілося б навчитися визначати пристрої, на яких спостерігається проблема з затримкою пуш-повідомлень. У цих цілях ми запилили статистику, суть якої – порівнювати різницю часу приходу пуша з часом, коли на сервері дані були готові для клієнта (коли був посланий пуш). І статистика показала, що приблизно у 20% користувачів спостерігається затримка з доставкою повідомлень. Але вона досить груба, оскільки в ній не враховуються кейси з зникненням мережі та іншим. Зараз ми думаємо над реалізацією такого алгоритму:
  1. Визначаємо, чи є затримка на цьому пристрої.
  2. Якщо так, то починаємо тримати постійне з'єднання з бекенд-сервером, ні – продовжуємо використовувати тільки GCM (в цілях економії батареї).


Десять хвилин взято з голови. Якщо дельта більше порогового значення, то перемикаємо додаток на режим роботи з власним з'єднанням.



В експериментальних цілях власне з'єднання було реалізовано шляхом http лонг-полінг з'єднання, т. к. це був найшвидший шлях для «спробувати». Така збірка була вислана кільком нашим бета-користувачів із проханням поспостерігати за доставкою повідомлень і витратою батареї. В цілому, як не дивно, не було різкого сплеску збільшення витрати батареї, а користувачі були задоволені швидкістю доставки повідомлень. Тема реалізації власного з'єднання з бекенд-сервером заслуговує окремої статті, а над самою реалізацією ми продовжуємо ще думати.

Сподіваюся, стаття буде корисною і дозволить скоротити ваш час при використанні сервісу GCM, а також буду радий, якщо поділіться своїм досвідом вирішення проблеми швидкості доставки повідомлення до клієнта в своїх додатках.

P. S. У дебажных цілях мною був написаний тестовий пуш-сервер — може, кому згодиться. Вихідний код тут.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.