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 сообщений.



    Получив такой intent, нужно не полениться и сходить на сервер за свежими данными. Иначе пользователь увидит их только когда придет очередное пуш-уведомление, а когда оно придет – никому неизвестно. Это очень важный момент, о котором нужно помнить при реализации подхода «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. В дебажных целях мною был написан тестовый пуш-сервер — может, кому пригодится. Исходный код тут.
    Mail.ru Group
    1,217.91
    Building the Internet
    Share post

    Comments 30

      0
      Hertbeat 8 минут не устанавливался по просьбе операторов сотовой связи, иначе:
      1. Впустую расходуются радио ресурсы на открытие и дальнейшее закрытие по бездействию канала.
      2. Это жрет батарею телефона так, что не возрадуешься.
      Не надо прикручивать ненужный краткосрочный пинг, он действительно не нужен.
        0
        Спасибо за информацию про операторов сотовой связи — не знал. Согласен с обоими пунктами. Краткосрочный пинг нужен приложениям, в которых задержка длиною в heartbeat недопустима (мессенджер или клиент охранной сигнализации к примеру), а так, да, нужно конечно свое соединение держать отдельное от gmc. Но, думаю, когда каждое приложение начнет держать свое соединение, то батарейка будет тратиться врядли меньше, чем если бы величина hearbeat была, к примеру, настраиваемой.
          0
          Вот тут есть пару слов о пингах и потреблении батареи, а тут о неэффективности периодических сеансов связи в мобильных сетях. Я не программист, больше по сетям связи, но уверен что есть правила и рекомендации по работе с передачей данных при разработке любых мобильных приложений.
            0
            Спасибо, ознакомлюсь.
        0
        А как быть в ситуации, когда GCM меняет устройству registration_id, а push сервер работает с notification_key? В таком случае push сервер вообще не знает (не обязан знать) registration_id устройств, и canonical_ids ничего полезного ему не скажут.
          0
          Интересный вопрос. Я не работал с notification_key на практике. И что-то с ходу в документации про это найти ничего не могу, кроме faled_registration_ids поля в ответе:
          {
            "from": "aUniqueKey",
            "message_type": "ack",
            "success":1,
            "failure":2,
            "failed_registration_ids":[
               "regId1",
               "regId2"
            ]
          }
          
          . Вы уверены, что пуш сервер не обязан знать registration_id каждого девайса в группе, разве не пуш-сервер должен добавлять/удалять registration_id в/из группы? Буду признателен, если расскажите как отслеживать изменение registration_id при использовании notification_key.
            0
            Задумка с notification_key была интересная: сервер приложения добавляет или удаляет идентификаторы устройств (registration_id) в группу, после чего можно отправлять сообщение сразу всей группе устройств. Тем самым мы экономим наши ресурсы за счёт ресурсов GCM. На практике возникли проблемы — по HTTP вообще не получалось отправить сообщение, хотя это было заявлено в документации. В каких-то случаях GCM сервер отвечал 500 при регистрации устройств в группе. Всё выглядело сыро, но, несмотря на это, у нас получилось реализовать работу по протоколу XMPP.
            Сегодня посмотрел в документацию и увидел, что гугл пометил эту технологию Deprecated.
            Вы уверены, что пуш сервер не обязан знать registration_id каждого девайса в группе, разве не пуш-сервер должен добавлять/удалять registration_id в/из группы?

            Сервер приложения (не обязательно пуш сервер) регистрирует идентификаторы устройств в группах, после чего непосредственно пуш сервер оперирует только идентификаторами групп для отправки сообщений. Чисто теоретически, даже само устройство может себя зарегистрировать в группе, но это небезопасно ввиду того, что приложению окажется доступен приватный ключ GCM.
            Буду признателен, если расскажите как отслеживать изменение registration_id при использовании notification_key.

            Пока не могу ответить на этот вопрос. Есть предположения, что заменой registration_id в группе должен заниматься GCM сервер, но, смотря опять же на сырость данной технологии, нельзя быть в этом уверенным. Ну и после объявления Deprecated было бы правильным вообще отказаться от неё.
              0
              По поводу Deprecated: не обратил внимание, что устаревшим помечен notification_key только для HTTP, но не для XMPP
                0
                Спасибо за подробное объяснение. Правильно я понимаю, что notification_key следует использовать, если надо ограничить круг девайсов (клиентов), которые могут получать уведомления и кол-во которых не может превышать 20 штук? И никто кроме них пуш-уведомления получать не должен? Иначе, не очень понимаю зачем он нужен.
                  +1
                  Самое простое, понятное и наглядное применение — объединение в группу всех устройств одного пользователя. Когда нам нужно отправить сообщение, нам надо его отправить прежде всего пользователю. И дабы не вспоминать идентификаторы всех устройств пользователя (registration_id), мы используем только идентификатор пользователя (notification_key), к которому уже привязаны на стороне GCM сервера идентификаторы всех его устройств.
                    0
                    Понял, спасибо.
          0
          Для своего соединения нужен foreground service по хорошему, иначе процесс будет убиваться где-то раз в час-два.
          Будет супер, если раскопаете более низкоуровневую информацию о соединении. Скажем, если есть девайс на котором heartbeat 8 минут поддерживает возможность мгновенного прихода соединения, а для heartbeat 10 минут — уже нет, то что именно изменяется на этой восьмой-девятой минуте? Приемник же до сих пор активен и может принять входящий звонок. Как это зависит от wi-fi/2g/3g/4g?
            0
            По хорошему скорее всего да, foreground service хороший кандидат, но висящая иконка в статус-баре многих раздражает (меня нет :)). Есть еще вариант со START_REDELIVER_INTENT и обычный сервис (либо IntentService). По поводу heartbeat хочу еще отметить, что проблему с ним, например, на nexus 4, nexus 5, nexus 7, 9 с чистым андроидом я не замечал, она отсутствует. А на samsung (s4, note) — да, стабильно.

            Скажем, если есть девайс на котором heartbeat 8 минут поддерживает возможность мгновенного прихода соединения, а для heartbeat 10 минут — уже нет, то что именно изменяется на этой восьмой-девятой минуте?


            Не совсем понял про «возможность мгновенного прихода соединения», поясните пожалуйста.
              0
              Ну это же про _Instant_ Messaging? Instant — это когда телефон сразу пиликает как только вам кто-то напишет, а не через пять минут или пару часов, даже если телефон в спячке. Как я понимаю, открытое tcp соединение может инициировать cpu wake-lock на входящих данных, и heartbeat нужен исключительно для того, чтоб сервак соединение не разрывал. Как следствие интересно, как heartbeat влияет на мнгновенность получения входящих соединений. Может быть большинство девайсов вообще не могут будить cpu на входящие сетевые воздействия? Про это и вопрос. Мне самому немного лень разбираться, т.к. сейчас никаким IM не занимаюсь, но интересно почитать.
                0
                А, я в начале опечатался немного — не соединения, а сообщения/пакета.
              0
              А существуют ли мессенджеры, лишенные проблемы с Heartbeat?
              На Android или Windows Phone или iPhone.
              Я на Nexus 4 менял таймаут Heartbeat. Ну… стало лучше, но не фонтан.
              А в Blackberry эта проблема решена в BES?
                0
                Могу сказать, что «проблем с HeartBeat» лишены те мессенджеры, которые использует помимо GCM свое собственное соединение. Думаю скоро это будут делать абсолютно все популярные мессенджеры, сейчас пока частично. Других способов решить эту проблему вроде как нет. Это я про Android. Про другие ОС ничего не могу сказать.
                  0
                  Думаю скоро это будут делать абсолютно все популярные мессенджеры, сейчас пока частично.

                  а кто щас уже? ну крупные есть брать whatsup, viper, telegramm и тд.
                0
                Мне однажды необходимо было реализовать постоянное соединение с бекэндом для получения свежих данных в приложении.

                В качестве решения был выбран довольно грубый, но действенный способ. При старте приложения создавал AlarmManager без повторения, который срабатывал через 2 минуты. В BroadcastReciver выполнял асинхронный запрос серверу, отменял AlarmManager и ставил заново. И так до бесконечности, пока не сработает метод остановки.
                Ещё один BroadcastReciver ловил запуск ОС и проверял, нужно ли переустановить AlarmManager.

                В результате получаем ничем не убиваемый процесс, который не держет постоянное соединение и точно достучаться к данным. Ну и батарею не садит сильно.
                  0
                  В результате получаем ничем не убиваемый процес

                  Судя по вашему описанию, ваш процесс убиваемый (ссылка на документацию ). Рекомендуют запускать Service из метода BroadcastReceiver.onReceive, это может отсрочить убийство системой вашего процесса. Либо использовать foreground service или service c флажком START_REDELIVER_INTENT .

                  При старте приложения создавал AlarmManager без повторения, который срабатывал через 2 минуты.

                  Если система убъет ваш процесс до того как выполнился асинхронный запрос, то следующий запуск AlarmManager не случится никогда, судя по вашему описанию или нет?
                  0
                  Ну, тестов провели много, запрещая фоновые процессы, ограничивая их число. Не убивался. Делал так: в onRecive первым делом переустанавливал AlarmManager и делал асинктаск.
                  Могу завтра привести пример кода, если интересно.
                    0
                    Да, очень любопытно взглянуть.
                      +1
                      Как и обещал.

                      Когда нужно запустить сервис по получению данных с бекэнда
                              // устанавливаем AlarmManager
                              AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
                              Intent intent = new Intent(this, OrderChecker.class);
                              PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0,
                                      intent, PendingIntent.FLAG_CANCEL_CURRENT);
                              // отменяем все установки, если они были
                              am.cancel(pendingIntent);
                              // устанавливаем на разовое выполнение 
                              int repeat = Integer.parseInt(profile_settings.getString("upd_period", "120000"));
                              am.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + repeat, pendingIntent);
                      


                      А вот BroadcastReceiver

                          @Override
                          public void onReceive(Context context, Intent intent) {
                                  // сохраняю контекст для других методов класса 
                                  myApp = context;
                                  db = new DatabaseHelper(myApp);
                                  profile_settings = PreferenceManager.getDefaultSharedPreferences(context);
                                  repeat = Integer.parseInt(profile_settings.getString("upd_period", "120000"));
                                  am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
                                  pendingIntent = PendingIntent.getBroadcast(context, 0,
                                          intent, PendingIntent.FLAG_CANCEL_CURRENT);
                                  try {
                                       // взяли из БД заказы
                                      orders = db.getOrdersFromDB(db.getReadableDatabase(),"");
                                      if (orders.size() == 0) {
                                           // если ничего нет, отменяем новый запуск
                                          am.cancel(pendingIntent);
                                      } else {
                                           // вот тут у меня AsyncTask 
                                          getNewStatusesFromServer(orders);
                                      }
                                  } catch (Exception e) {
                                       // если словил любую ошибку, устанавливаю новый запуск AlarmManager
                                      am.cancel(pendingIntent);
                                      am.set(AlarmManager.ELAPSED_REALTIME,
                                              SystemClock.elapsedRealtime() + repeat, pendingIntent);
                                  }
                          }
                      
                          private void getNewStatusesFromServer(List<String> orders) {
                                       // некоторая реализация 
                                       // и в конце я устанавливаю новый запуск AlarmManager
                                      am.cancel(pendingIntent);
                                      am.set(AlarmManager.ELAPSED_REALTIME,
                                              SystemClock.elapsedRealtime() + repeat, pendingIntent);
                              }
                      


                      получается, что onReceive отработал и установил новый таймер — сработать через 2 минуты. И так пока orders.size() != 0

                      Если телефон перезагрузили
                      public class CheckerStarter extends BroadcastReceiver {
                          @Override
                          public void onReceive(Context context, Intent i) {
                              if ("android.intent.action.BOOT_COMPLETED".equals(i.getAction()) || "android.intent.action.QUICKBOOT_POWERON".equals(i.getAction())) {
                                  AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
                                  Intent intent = new Intent(context, OrderChecker.class);
                                  PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
                                          intent, PendingIntent.FLAG_CANCEL_CURRENT);
                                  am.cancel(pendingIntent);
                                  int repeat = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context).getString("upd_period", "120000"));
                                  am.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + repeat, pendingIntent);
                              }
                          }
                      }
                      


                      З.Ы. Я заранее говорил, что метод очень грубый. Но это позволило не держать постоянный коннект и не держать отдельный поток с Thread.sleep(120000). За 2 минуты простоя ось не отменит AlarmManager из-за нехватки ресурсов.
                        0
                        1. Грузить заказы из базы данных в методе
                         onReceive 
                        очень грубое решение) Этот метод вызывается в UI потоке.
                        2. Хорошей практикой считается запускать сервис из метода onReceive дабы не делать слишком многого в нем
                        3. В принципе, как вы сами говорите, решение грубое. Утверждать, что за 2 минут простоя ОС не убьет ваш процесс я бы не стал :)

                        Перенеся am.cancel am.set в onReceive() мы будем уверенны, что новый запуск AlarmManager будет установлен, даже если в какой-то момент андроид прибил наш AsyncTask.

                        Вот про это не совсем понял, кто будет устанавливать новый запуск аларм менеджера?
                          0
                          1. Грузить заказы из базы данных в методе
                          onReceive
                          очень грубое решение) Этот метод вызывается в UI потоке.

                          Так это sqlite, внутренняя БД. + там только заказы не в конечном статусе, по которым понадобиться push.

                          2. Хорошей практикой считается запускать сервис из метода onReceive дабы не делать слишком многого в нем

                          Как я понял из документации, сервисы, потоки, работающие после завершения работы onRecive могут быть убиты

                          3. В принципе, как вы сами говорите, решение грубое. Утверждать, что за 2 минут простоя ОС не убьет ваш процесс я бы не стал :)

                          Так это ж AlarmManager. Он как раз и не висит потоком. Ось же не убивает установки будильника :)

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

                          Я же и устанавливаю в onRecive. У меня есть интент. Из него получить PendingIntent не проблема. Ну и флаг FLAG_CANCEL_CURRENT заменяет AlarmManager:
                          pendingIntent = PendingIntent.getBroadcast(context, 0,
                                              intent, PendingIntent.FLAG_CANCEL_CURRENT);
                          
                        0
                        Как я понял из документации, после отработки onReceive(), Андроид может убить процесс, если даже мы запустили где-то свой поток (AsyncTask).
                        Эту проблему можно побороть так:
                                        if (orders.size() == 0) {
                                             // если ничего нет, отменяем новый запуск
                                            am.cancel(pendingIntent);
                                        } else {
                                             // вот тут у меня AsyncTask 
                                            getNewStatusesFromServer(orders);
                                             // вот тут мы пересоздаем AlarmManager
                                        am.cancel(pendingIntent);
                                        am.set(AlarmManager.ELAPSED_REALTIME,
                                                SystemClock.elapsedRealtime() + repeat, pendingIntent);
                                        }
                        
                            private void getNewStatusesFromServer(List<String> orders) {
                                         // некоторая реализация 
                                }
                        
                        


                        Перенеся am.cancel am.set в onReceive() мы будем уверенны, что новый запуск AlarmManager будет установлен, даже если в какой-то момент андроид прибил наш AsyncTask.

                        Если ошибаюсь, поправьте…
                          +1
                          Метод вполне рабочий, если закрыть глаза на возможность ANR при чтении БД и то, что это высасывает батарейку будя CPU и радиомодуль каждые две минуты. Это особенно будет заметно на андроид M, где это по факту выключит Doze режим. Не хотел бы я такое на своем телефоне иметь, если такое постоянно крутится.
                            0
                            на возможность ANR при чтении БД

                            Почему должен быть ANR при чтении с БД?

                            и то, что это высасывает батарейку будя CPU и радиомодуль каждые две минуты

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

                            В любом случае это делалось в приложении для такси и таким образом отслеживалось изменение статуса заказа. А тут очень важно было быстро прислать пуш клиенту, что его машина приехала или взят заказ. GCM тут не удобен
                              0
                              ANR при чтении возможен на старых девайсах когда происходит активная запись, например, установка игрушки или что-то в этом стиле.

                              Коннект я думаю можно держать почти бесплатно если железо грамотно это поддерживает. Например, телефон же как-то знает, что идет входящий звонок и для этого wake-lock держать точно не нужно, даже если текущее соединение — lte.
                                0
                                Да, из опыта держать открытый коннект батарею сильно не садит. GCM работает именно так — он просто держит xmpp соединение, правда процесс видимо whitelisted благодаря чему андроид его не убивает периодически, не смотря на то, что он не foreground importance.

                                Я не говорю, что решение не самое оптимальное для этого применения, просто говорю что возможны неприятности такого плана.

                    Only users with full accounts can post comments. Log in, please.