Почему не работает пуш уведомления. Push уведомления в Android

19.05.2019
28 декабря 2015 в 12:25

Push уведомления в Android. Грабли, костыли и велосипеды

  • Разработка мобильных приложений ,
  • Разработка под Android

На написание данной статьи меня подтолкнула задача, которая была поставлена передо мной в одном из рабочих проектов: реализовать Push-уведомления в приложении. Казалось, все просто: штудируешь документацию, примеры и вперед. К тому же, опыт работы с уведомлениями уже был. Но не тут то было…

Сервис, в рамках которого реализовано приложение под Android, предъявляет довольно жесткие требования к работе Push-уведомлений. Необходимо в пределах 30-60 секунд оповестить пользователя о некотором действии. При успешном оповещении с устройства пользователя отправляется запрос на сервер с соответствующим статусом. Из документации известно, что сервис GCM (Google Cloud Messaging) не гарантирует доставку PUSH-уведомлений на устройства, поэтому в качестве backdoor варианта, при нарушении этих временных рамок, наш сервис уведомляет пользователя с помощью SMS сообщения. Поскольку стоимость SMS сообщения существенно выше чем PUSH-уведомления, необходимо максимально сократить поток SMS сообщений на клиентские устройства.

Проштудировав документацию и прикрутив пуш-уведомления, мы разослали нескольким клиентам первую сборку приложения для теста и стали ждать. Результаты были примерно следующими:

  • при активном Wifi соединении все работает идеально: уведомления доставляются, клиенты рады.
  • при активном мобильном интернете началось самое веселье.
Некоторые клиенты писали, что испытывают задержки в доставке пушей, либо получали одновременно и PUSH и SMS, что достаточно не практично. Другие писали, что вовсе не получали уведомлений, а только SMS. У третьих, как и у нас на тестовых устройствах, все было ок. Собрав с недовольных клиентов максимально возможную информацию, стали разбираться в проблеме и вывели следующий список ограничений (этот список позже вылился в полноценный FAQ):
  • включенный режим Энергосбережения (например, Stamina на устройствах Sony) влияет на работу Push уведомлений;
  • у пользователя обязательно должен быть минимум 1 активный Google аккаунт на устройстве;
  • необходимо удостовериться в том, что на устройстве установлена актуальная версия приложения “Сервисы Google Play”;
  • проверить, не отключены ли уведомления для приложения (галочка на страничке приложения в настройках телефона);
  • проверить, не ограничена ли работа фонового режима для приложения (настройка расположена в меню «Использование данных»);
  • в документации к GCM указано, что уведомления рассылаются только по определенным портам, поэтому настройки роутера, файервола и антивируса так же стоит учитывать.
Разослав данную памятку по всем клиентам, мы снова стали ждать результатов. И они оказались снова «не очень». Стали копать дальше.

На данном этапе очень сильно помогла , написанная ребятами из Mail.ru. В ней очень подробно описаны тонкости реализации GCM на клиентской стороне, а так же моменты, в связи с которыми отказываются работать Push уведомления в мобильных сетях. В конечном счете было принято решение о том, чтобы держать свое соединение с сервером в связке с GCM.

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

  • проблема возникает только при подключении к мобильному интернету;
  • по данным клиентов, проблема возникает на версии андроида 4 и выше.
И так, перейдем к реализации.

Бывалый разработчик под Android сходу скажет, что решений задачи как минимум 2: использовать Service или AlarmManager. Мы попробовали оба варианта. Рассмотрим первый из них.

Для того, чтобы создать неубиваемый системой сервис, который постоянно будет висеть в фоне и выполнять нашу задачу, мы использовали метод:

StartForeground(int notificationID, Notification notification);
где

  • notificationId - некоторый уникальный идентификатор уведомления, который будет выведен в статус баре и в выезжающей шторке;
  • notification - само уведомление.
В данном случае обязательным условием является отображение уведомления в статус баре. Такой подход гарантирует то, что сервису будет дан больший приоритет (поскольку он взаимодействует с UI частью системы) в момент нехватки памяти на устройстве и система будет выгружать его одним из последних. Нам это уведомление не нужно, поэтому мы воспользовались следующим велосипедом: достаточно запустить одновременно с первым сервисом второй и для обоих сервисов в качестве notificationID использовать одно и тоже значение. Затем убить второй сервис. При этом уведомление пропадет из статус бара, но функциональные и приоритетные возможности первого сервиса останутся.

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

AlarmManager - это класс, который предоставляет работу с, грубо говоря, «будильником». Он позволяет указать время, по достижении которого система отправит широковещательное уведомление, которое позволит пробудить наше приложение и даст ему возможность выполнить необходимые действия. В работе этого метода есть некоторые ограничения, и их необходимо обработать:

  • данные о «будильниках» будут стерты после перезагрузки устройства;
  • данные о «будильниках» будут стерты после обновления приложения.
Первыми граблями, на которые мы наступили, был метод

SetRepeating()
который позволяет установить повторяющийся с некоторым интервалом «будильник». Прикрутив данный способ, стали тестировать, и тесты показали обратное - «будильник» не повторялся. Стали разбираться в чем дело, посмотрели документацию. И именно там нашли ответ на вопрос - начиная с 19 API lvl (Kitkat) абсолютно все «будильники» в системе стали разовыми. Вывод - всегда читайте документацию.

Эти грабли не были поводом для расстройства, ведь решение задачи довольно простое - запускать единоразовый «будильник» и после срабатывания переустанавливать его. При реализации этого подхода мы наткнулись на следующие грабли - оказалось, что для разных уровней API необходимо по разному устанавливать будильники, при этом в документации ничего сказано не было. Но данная проблема решилась достаточно просто - методом «тыка» и «гугления». Ниже представлен пример кода, позволяющий правильно устанавливать «будильники»:

Private static void setUpAlarm(final Context context, final Intent intent, final int timeInterval) { final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); final PendingIntent pi = PendingIntent.getBroadcast(context, timeInterval, intent, 0); am.cancel(pi); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { final AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + timeInterval, pi); am.setAlarmClock(alarmClockInfo, pi); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeInterval, pi); else am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeInterval, pi); }
Хочу обратить внимание на флаг AlarmManager.RTC_WAKEUP - именно с помощью него система позволит нашему приложению «проснуться» при неактивном экране, когда устройство находится в заблокированном состоянии.

Данный подход с «будильникам» дал нам нужный результат - приложение в фоне корректно опрашивает сервер на наличие новых данных. Сейчас мы дорабатываем алгоритм. На данный момент реализуем и тестируем следующую оптимизацию, которая позволит сузить круг устройств и тем самым уменьшить нагрузку на сервер:

  • в сообщении, отправленном средствами GCM на устройство, содержится некоторый уникальный ID;
  • получив данные GET запросом в фоновом режиме проверяем, существуют ли уже запись с таким ID на устройстве;
  • если локально на устройстве таких данных нет, мы запоминаем этот ID и время его получения T1;
  • ждем PUSH с таким же ID, при получении запоминаем время T2 и проверяем разницу между T2 и T1;
  • если разница составляет больше некоторого временного критерия (значения), то на устройстве наблюдается проблема с доставкой уведомлений и для корректной работы сервиса необходимо постоянно запрашивать данные в фоновом режиме с сервера (критерий советую выбирать исходя из решаемой задачи. В нашем случае, был выбран критерий равный 5 минутам);
  • данную разницу стоит вычислять несколько раз, например 5-10 раз, только после этого делать вывод о том, что устройство действительно содержит проблему с получением Push уведомлений (таким образом исключается ситуация банального разрыва соединения, таймаута и пр.);
  • необходимо прогонять данный алгоритм периодически (например, раз в неделю, или после обновления ОС на устройстве).
Всем добра. И поменьше подобных костылей.

P.S.
В процессе тестирования очень помог

На написание данной статьи меня подтолкнула задача, которая была поставлена передо мной в одном из рабочих проектов: реализовать Push-уведомления в приложении. Казалось, все просто: штудируешь документацию, примеры и вперед. К тому же, опыт работы с уведомлениями уже был. Но не тут то было…

Сервис, в рамках которого реализовано приложение под Android, предъявляет довольно жесткие требования к работе Push-уведомлений. Необходимо в пределах 30-60 секунд оповестить пользователя о некотором действии. При успешном оповещении с устройства пользователя отправляется запрос на сервер с соответствующим статусом. Из документации известно, что сервис GCM (Google Cloud Messaging) не гарантирует доставку PUSH-уведомлений на устройства, поэтому в качестве backdoor варианта, при нарушении этих временных рамок, наш сервис уведомляет пользователя с помощью SMS сообщения. Поскольку стоимость SMS сообщения существенно выше чем PUSH-уведомления, необходимо максимально сократить поток SMS сообщений на клиентские устройства.

Проштудировав документацию и прикрутив пуш-уведомления, мы разослали нескольким клиентам первую сборку приложения для теста и стали ждать. Результаты были примерно следующими:

  • при активном Wifi соединении все работает идеально: уведомления доставляются, клиенты рады.
  • при активном мобильном интернете началось самое веселье.
Некоторые клиенты писали, что испытывают задержки в доставке пушей, либо получали одновременно и PUSH и SMS, что достаточно не практично. Другие писали, что вовсе не получали уведомлений, а только SMS. У третьих, как и у нас на тестовых устройствах, все было ок. Собрав с недовольных клиентов максимально возможную информацию, стали разбираться в проблеме и вывели следующий список ограничений (этот список позже вылился в полноценный FAQ):
  • включенный режим Энергосбережения (например, Stamina на устройствах Sony) влияет на работу Push уведомлений;
  • у пользователя обязательно должен быть минимум 1 активный Google аккаунт на устройстве;
  • необходимо удостовериться в том, что на устройстве установлена актуальная версия приложения “Сервисы Google Play”;
  • проверить, не отключены ли уведомления для приложения (галочка на страничке приложения в настройках телефона);
  • проверить, не ограничена ли работа фонового режима для приложения (настройка расположена в меню «Использование данных»);
  • в документации к GCM указано, что уведомления рассылаются только по определенным портам, поэтому настройки роутера, файервола и антивируса так же стоит учитывать.
Разослав данную памятку по всем клиентам, мы снова стали ждать результатов. И они оказались снова «не очень». Стали копать дальше.

На данном этапе очень сильно помогла , написанная ребятами из Mail.ru. В ней очень подробно описаны тонкости реализации GCM на клиентской стороне, а так же моменты, в связи с которыми отказываются работать Push уведомления в мобильных сетях. В конечном счете было принято решение о том, чтобы держать свое соединение с сервером в связке с GCM.

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

  • проблема возникает только при подключении к мобильному интернету;
  • по данным клиентов, проблема возникает на версии андроида 4 и выше.
И так, перейдем к реализации.

Бывалый разработчик под Android сходу скажет, что решений задачи как минимум 2: использовать Service или AlarmManager. Мы попробовали оба варианта. Рассмотрим первый из них.

Для того, чтобы создать неубиваемый системой сервис, который постоянно будет висеть в фоне и выполнять нашу задачу, мы использовали метод:

StartForeground(int notificationID, Notification notification);
где

  • notificationId - некоторый уникальный идентификатор уведомления, который будет выведен в статус баре и в выезжающей шторке;
  • notification - само уведомление.
В данном случае обязательным условием является отображение уведомления в статус баре. Такой подход гарантирует то, что сервису будет дан больший приоритет (поскольку он взаимодействует с UI частью системы) в момент нехватки памяти на устройстве и система будет выгружать его одним из последних. Нам это уведомление не нужно, поэтому мы воспользовались следующим велосипедом: достаточно запустить одновременно с первым сервисом второй и для обоих сервисов в качестве notificationID использовать одно и тоже значение. Затем убить второй сервис. При этом уведомление пропадет из статус бара, но функциональные и приоритетные возможности первого сервиса останутся.

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

AlarmManager - это класс, который предоставляет работу с, грубо говоря, «будильником». Он позволяет указать время, по достижении которого система отправит широковещательное уведомление, которое позволит пробудить наше приложение и даст ему возможность выполнить необходимые действия. В работе этого метода есть некоторые ограничения, и их необходимо обработать:

  • данные о «будильниках» будут стерты после перезагрузки устройства;
  • данные о «будильниках» будут стерты после обновления приложения.
Первыми граблями, на которые мы наступили, был метод

SetRepeating()
который позволяет установить повторяющийся с некоторым интервалом «будильник». Прикрутив данный способ, стали тестировать, и тесты показали обратное - «будильник» не повторялся. Стали разбираться в чем дело, посмотрели документацию. И именно там нашли ответ на вопрос - начиная с 19 API lvl (Kitkat) абсолютно все «будильники» в системе стали разовыми. Вывод - всегда читайте документацию.

Эти грабли не были поводом для расстройства, ведь решение задачи довольно простое - запускать единоразовый «будильник» и после срабатывания переустанавливать его. При реализации этого подхода мы наткнулись на следующие грабли - оказалось, что для разных уровней API необходимо по разному устанавливать будильники, при этом в документации ничего сказано не было. Но данная проблема решилась достаточно просто - методом «тыка» и «гугления». Ниже представлен пример кода, позволяющий правильно устанавливать «будильники»:

Private static void setUpAlarm(final Context context, final Intent intent, final int timeInterval) { final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); final PendingIntent pi = PendingIntent.getBroadcast(context, timeInterval, intent, 0); am.cancel(pi); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { final AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + timeInterval, pi); am.setAlarmClock(alarmClockInfo, pi); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeInterval, pi); else am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeInterval, pi); }
Хочу обратить внимание на флаг AlarmManager.RTC_WAKEUP - именно с помощью него система позволит нашему приложению «проснуться» при неактивном экране, когда устройство находится в заблокированном состоянии.

Данный подход с «будильникам» дал нам нужный результат - приложение в фоне корректно опрашивает сервер на наличие новых данных. Сейчас мы дорабатываем алгоритм. На данный момент реализуем и тестируем следующую оптимизацию, которая позволит сузить круг устройств и тем самым уменьшить нагрузку на сервер:

  • в сообщении, отправленном средствами GCM на устройство, содержится некоторый уникальный ID;
  • получив данные GET запросом в фоновом режиме проверяем, существуют ли уже запись с таким ID на устройстве;
  • если локально на устройстве таких данных нет, мы запоминаем этот ID и время его получения T1;
  • ждем PUSH с таким же ID, при получении запоминаем время T2 и проверяем разницу между T2 и T1;
  • если разница составляет больше некоторого временного критерия (значения), то на устройстве наблюдается проблема с доставкой уведомлений и для корректной работы сервиса необходимо постоянно запрашивать данные в фоновом режиме с сервера (критерий советую выбирать исходя из решаемой задачи. В нашем случае, был выбран критерий равный 5 минутам);
  • данную разницу стоит вычислять несколько раз, например 5-10 раз, только после этого делать вывод о том, что устройство действительно содержит проблему с получением Push уведомлений (таким образом исключается ситуация банального разрыва соединения, таймаута и пр.);
  • необходимо прогонять данный алгоритм периодически (например, раз в неделю, или после обновления ОС на устройстве).
Всем добра. И поменьше подобных костылей.

P.S.
В процессе тестирования очень помог

Любители социальной сети Вконтакте часто сталкиваются с проблемой отсутствия push-уведомления. Не всплывают окна с быстрыми диалогами, что вносит некоторое неудобство. Проблемы могут быть различными, постараемся рассказать о них и разобраться с методами решения.

Проблемные моменты с уведомлениями в ВК

На самом деле, разобраться с каждой проблемой не сложно. Среди них можно выделить следующие:

  1. Обновления сайта социальной сети, перегруженность серверов и другие технические моменты. Часто можно встретиться с подобным, когда происходят глобальные изменения в социальной сети. Многие функции оказываются недоступными, включая push-уведомления;
  2. Сбой глобальный настроек аккаунта. Возможно, вы сами или ваши родственники могли случайно или намерено сбить некоторые параметры в VK. Решение проблемы связываться с проверкой отдельных параметров и возврат нужных значений в исходное положение;
  3. Изменение настроек в закрытых диалогах с несколькими людьми. Появление сообщений весьма часто раздражает назойливостью, поэтому некоторые пользователи выключают отдельные элементы и жалуются на отсутствие всплывающих окон;
  4. Браузер не поддерживает java-скрипты. Конечно, это редкость сегодня, но проблемные моменты возникают. Тут поможет только одно средство! Что именно, читайте далее в статье.

Технические моменты: ожидание и обновление

В этом разделе попытаемся решить проблемы с пунктом №1 и 4. Что касается первого, то здесь все достаточно просто. Обновления и перегрузки системы – это временное явление, вам потребуется терпение и ожидание. Сколько продлятся такие неудобства? Все зависит от конкретной ситуации:

  1. Если проводятся большая доработка отдельных элементов сайта, то процесс займет до 8 часов. За это время будет выполнена перезагрузка отдельных модулей, наладка связей и проверка работоспособности. Чтобы отслеживать обновления, рекомендуем подписаться на официальную группу разработчиков в VK;
  2. Перегрузка сервера также может стать проблемным моментом для работы с уведомлениями. Сейчас разработчики снизили нагрузку на сайт, но иногда столкнуться с явлением, особенно летом, возможно. Решение одно, ожидание и надежда на самостоятельное исправление ситуации. В случае возникновения «красных ошибок», рекомендуем почистить кэш браузера.

Про приложения для работы в веб-среде отдельная история. Некоторые программы не поддерживают java-скрипты или просто заблокированы в глобальных настройках. Используйте другое программное обеспечение, сбросьте все до первоначальных значений или переустановите софта самой свежей версии. С этим разобрались, теперь переходим к параметрам для Вконтакте.

Неправильные или сбитые настройки


Часто проблема появляется из-за неправильных или сбитых параметров. Решить вопрос удается через ряд следующие действия:

  1. Требуется перейти в соответствующий раздел. Это выполняется путем нажатия на иконку с именем в правом верхнем углу;
  2. В новом окне «Настройки» потребуется отыскать раздел «Уведомления», после на центральном экране возникнут ползунки. Они должны быть в правом положении. Если это не так, исправьте;
  3. В случае с массовыми диалогами, необходимо проверить включенность соответствующей функции непосредственно в диалоге. Для этого переходим в нужный чат, находим три точки сверху, и выбираем «Отключить/включить уведомления».

Проблема решена, в некоторых случаях потребуется выйти и войти в систему, почистить кэш. Надеемся советы помогли, и вы устранили неудобства. Также нужно обратить внимание, что VK использует уведомления на почту и через SMS, об этом поговорим в будущих статьях.