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

 

Введение

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

Компания Google разработала API под названием Google Cloud Messaging, он доступен как для Android, так и для iOS. Использовать этот сервис можно совершенно бесплатно, то есть даром. Посмотрим, что же это за зверь и чем он хорош!

Google Cloud Messaging работает на всех ОС Android версии 2.2 и выше. Он предоставляет возможность удаленно отправить сообщение в приложение, ранее установленное на мобильное устройство. Полезный объем данных ограничен четырьмя килобайтами, при этом содержание таких сообщений может быть произвольным: от текстового уведомления для пользователя до системных команд приложению. В случае если мобильное устройство не в сети или включен режим энергосбережения, GCM создаст очередь из сообщений и доставит их позднее.

 

Как это работает

Во многих устройствах на базе Android есть приложения от Google с различной функциональностью, за правильную работу Push-сообщений отвечает пакет Google Play Services. Если такой пакет установлен, устройство периодически обращается к серверу с запросом о наличии новых Push-сообщений. Частота таких запросов зависит от многих параметров: способа интернет-соединения, состояния батареи и прочего. При этом для экономии трафика у пользователя есть возможность вообще отключить такой сервис.

Push-серверы служат «прослойкой» между устройством-получателем и отправителем сообщения. От разработчика, использующего GCM, требуется составить сообщение, следуя несложному синтаксису, а затем отправить POST-запрос на сервер Google, который в дальнейшем самостоятельно доставит сообщение на устройство.

Отправленное на устройство Push-сообщение имеет единственного конечного адресата — это приложение с уникальным идентификатором, другие приложения доступа к нему не получат. Для обработки полученных сообщений в приложении нужно реализовать методы, доступные через Google Play Services. Этим сегодня и займемся.

Рис. 1. Схема работы Google Cloud Messaging
Рис. 1. Схема работы Google Cloud Messaging
 

Регистрация

Как ты уже мог догадаться, главное звено в сервисе GCM — это серверы Google. Облако от этой компании осуществляет пропускной контроль и берет на себя все хлопоты по доставке сообщений, поэтому первым шагом будет регистрация в системе.

Для работы с GCM требуется создать несколько ключей доступа, их выдают после регистрации приложения на сайте Google (ссылка есть во врезке). Нужно указать точное название приложения и полное имя проекта. В результате ты получишь два уникальных параметра: Server Key и Sender ID. Важный момент: если проект или название приложение изменятся, ключи придется генерировать заново.

Также на странице регистрации есть большая синяя кнопка Generation configuration files. Она сформирует конфиг-файл google-services.json, в котором зашиты все необходимые параметры для работы сервиса, в том числе ключи. Чтобы добавить этот конфиг-файл в проект, просто скопируй его в корень папки app, где лежат исходные коды приложения.

Приложение будет использовать готовые инструменты, реализованные в Google Play Services, поэтому нужно подключить дополнительные модули к проекту. В Android Studio это делается путем правки файлов Gradle: в конфиг проекта подгружаем класс Google Services, а в конфиге приложения — плагины GCM и Google Play.

classpath 'com.google.gms:google-services:2.0.0-alpha6'
apply plugin: 'com.google.gms.google-services'
compile "com.google.android.gms:play-services-gcm:8.3.0"
 

Манифест-файл

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

<uses-permission android:name="android.permission.WAKE_LOCK" />

Чтобы стать получателем Push-сообщений, нужно добавить разрешение под названием C2D_MESSAGE, при этом в параметре name должно быть полное имя пакета.

<permission android:name="com.pahomov.gcmproject.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="com.pahomov.gcmproject.permission.C2D_MESSAGE" />

В нашем приложении будут модули из Google Play Service, об этом нужно тоже написать в манифесте. Сервис GcmReceiver отвечает за прием сообщений от сервера, а также в случае необходимости помогает устройству не уйти в режим экономии энергии:

<receiver android:name="com.google.android.gms.gcm.GcmReceiver" android:exported="true" ...></receiver>

Обрати внимание, что еще совсем недавно (до октября прошлого года) Push-уведомления можно было отправить сервисом Cloud to Device Messaging, подключая соответствующее разрешение c2dm.permission.RECEIVE, теперь он окончательно закрыт.

 

Проверки

Как показывает практика использования различных API от Google, генерация ключей и устранение зависимостей — самые трудоемкие задачи. Реализация же логики API в проекте довольно проста: нужно только создать несколько объектов и переопределить их классы, отвечающие за получение и отправку данных. На устройстве может не быть приложений от Google, в этом случае придется отказаться от Push-сообщений. Проверка на наличие API выполняется следующим образом.

private boolean checkPlayServices() {
  GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
  int resultCode = apiAvailability.isGooglePlayServicesAvailable(this);
  if (resultCode = ConnectionResult.SUCCESS) {return true;}
}
 

Токен

Ввести приложение в элитный клуб получателей Push-сообщений поможет один из методов Google API. Вызвать его будет удобно, воспользовавшись классом IntentService. Так же как и его родительский класс Service, он позволяет выполнять в фоне различные ресурсоемкие задачи, например сетевые вызовы. Главное преимущество IntentService в том, что после выполнения всех задач он будет самостоятельно остановлен системой, тогда как обычный Service продолжит висеть в фоне, ожидая команды stopSelf() или stopService(). Класс регистрации в GCM назовем RegistrationIntentService.

public class RegistrationIntentService extends IntentService {
  ...
}

Собственно, разработчики Google все подготовили для нас, осталось только нажать большую красную кнопку и наслаждаться результатом. В GCM API есть класс InstanceID, способный самостоятельно сформировать сетевой пакет с регистрационными данными и отправить его на сервер. От разработчика требуется только указать уникальный идентификатор пакета (Sender ID, еще он иногда называется Project number). А если файл google-services.json был скопирован правильно, то переменная gcm_defaultSenderId сама появится в Android Studio.

protected void onHandleIntent(Intent intent) {
  InstanceID instanceID = InstanceID.getInstance(this);
  String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
  ...
}

Метод getToken возвращает еще один уникальный параметр — идентификатор устройства, на котором установлено приложение с включенным сервисом GCM. Этот токен пригодится чуть позже для отправки сообщений, поэтому его нужно как-то передать из приложения на сервер разработчика. В этот раз Google, к сожалению, не поможет, сетевую функциональность нужно реализовать самому. Но не так давно в статье о библиотеках я писал про Retrofit, тут она будет как раз к месту.

Компания Google рекомендует периодически обновлять токен, чтобы избежать его компрометации и использования злоумышленниками. Чтобы инициировать из приложения перевыпуск ключа, достаточно просто еще раз вызвать метод getToken. Еще можно с сервера отправить команду на обновления токена, за обработку такой команды в приложении отвечает класс InstanceIDListenerService. Наличие в приложении такого сервиса обязательно, без него GCM не заработает.

public class MyInstanceIDListenerService extends InstanceIDListenerService {}

В этом классе существует единственный метод onTokenRefresh, он будет вызван системой в тот момент, когда приложение получит команду на обновление. Основная задача этого метода — запустить IntentService, содержащий InstanceID.

public void onTokenRefresh() {
  Intent intent = new Intent(this, RegistrationIntentService.class);
  startService(intent);
}
 

Сообщения

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

Push-сообщения имеют организованную структуру с обязательными полями, необходимыми для авторизации отправителя и получателей. Для удобства разработчиков сообщения могут быть в формате JSON или XML, сегодня мы будем использовать JSON как более прогрессивный.

 

От сервера

Концепция GCM предполагает, что разработчик обладает веб-сервером, с которого будет идти рассылка сообщений. Для отправки сообщения с сервера на мобильное устройство (downstream message) нужно отправить запрос вот такого формата:

https://gcm-http.googleapis.com/gcm/send
Content-Type:application/json
Authorization:key=Sever_key
{
  "data": {"message": "hello world", "extra": "more info"},
  "to": "app_token"
}

Формат общения не так сложен: на сервер Google методом POST отправляется HTTP-запрос, в теле которого содержится JSON-сообщение. Отправитель идентифицируется с помощью ключа Server key, который был выдан при регистрации, а получателем сообщения будет одно конкретное устройство, обладающее токеном, полученным из InstanceID.

 

Топики

Вполне вероятна ситуация, когда требуется отправить одинаковое сообщение сразу группе устройств. Для этого не нужно закидывать Google одинаковыми запросами, достаточно поменять адресата. В GCM есть возможность организовать своего рода подписку на топики новостей: чтобы создать рассылку всем подписчикам, достаточно в поле получателя указать что-то вроде

"to" : "/topics/xakep"

В этом примере сообщение получат все устройства, которые подписаны на топик xakep. Отмечу, что параметр "/topics/" является обязательным и неизменным.

 

Группы

Подписка на топики делается только внутри приложения, что не всегда удобно. Есть возможность со стороны сервера объединить несколько устройств в группу и рассылать им сообщения. Вот как будет создана группа pahomovGroup.

https://android.googleapis.com/gcm/notification
Content-Type:application/json
Authorization:key=Sender_key
project_id:Sender_id
{
  "operation": "create",
  "notification_key_name": "pahomovGroup",
  "registration_ids": ["app1_token", "app2_token"]
}

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

 

От клиента

Приложение с реализованным GCM может посылать сообщения другим устройствам, на которых установлено приложение с такими же идентификаторами (upstream message). Для такой задачи есть класс с неожиданным именем GoogleCloudMessaging:

GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);

Отправить сообщение возможно какому-то конкретному приложению или на сервер с поднятым сервисом XMPP, разослать послание группе или подписчикам топика, к сожалению, нельзя.

Данные для сообщения задаются через класс Bundle путем генерации связки ключ — значение. Чтобы сформировать пакет и отправить запрос, достаточно вызвать метод send с указанием токена получателя, уникального ID сообщения (случайное число) и полезной нагрузки:

Bundle data = new Bundle();
data.putString("Message", "hello");
gcm.send(to, id, data);
 

Получение сообщений

Получать сообщения довольно просто — в Google API, который мы уже подключили, доступен класс GcmListenerService. Его нужно явно объявить в проекте и переопределить единственный метод. Когда на устройство поступит новое сообщение, в приложении будет вызван метод onMessageReceived. В аргументах будут все необходимые данные: имя отправителя и тело POST-запроса, обернутое в объект класса Bundle. Так как в JSON информация хранится по принципу ключ — значение, получить данные из поля extra возможно вот таким способом:

public class MyGcmListenerService extends GcmListenerService {
  public void onMessageReceived(String from, Bundle data) {
    String messageExtra = data.getString("extra");
    ...
  }
}

Уведомления для пользователя выводятся с помощью классов NotificationManager и NotificationCompat.Builder. Сначала задается структура сообщения: его заголовок, основной текст и иконка, которая будет видна в панели уведомлений:

NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
  .setSmallIcon(R.mipmap.ic_launcher)
  .setContentTitle("New message")
  .setContentText(message)

Затем нужно подключиться к сервису в Android, который отвечает за все уведомления в системе:

NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

Вывод сообщения осуществляет метод notify, при этом сообщению нужно придумать уникальный внутри приложения идентификатор.

notificationManager.notify(0, notificationBuilder.build());
Рис. 2. Пример уведомления
Рис. 2. Пример уведомления
 

Подписываемся на топики

Теперь реализуем подписку на топики, о которых мы говорили ранее. Чтобы подписаться на набор топиков, достаточно в цикле вызвать метод subscribe из класса GcmPubSub.

String[] TOPICS = {"xakep","pahomov"} ;
GcmPubSub pubSub = GcmPubSub.getInstance(this);
for (String topic : TOPICS) {
  pubSub.subscribe(token, "/topics/" + topic, null);
}

Теперь, если устройство получит сообщение, которое адресовано подписчикам таких топиков, будет вызван метод onMessageReceived.

 

Генерация сообщений

Сформировать POST-запрос по указанным требованиям можно разными способами. Если под рукой есть Linux-машина с консолью, то рекомендую тебе утилиту cURL, она позволяет отправлять и принимать сетевые запросы самых разных форматов. К примеру, вот так будет выглядеть сообщение от сервера с указанием токена:

curl -s "https://android.googleapis.com/gcm/send" \
  -H "Authorization: key=Server_key" \
  -H "Content-Type: application/json" \
  -d '{"to": "app_token", "from":"xakep", "data": {"message": "hello", "extra": "world"}}'
 

Troubleshooting

При использовании GCM больше всего вопросов возникает на этапе подготовки. Использование самых свежих библиотек требует актуальной версии сборщика: модули Google Play Services, которые мы подключали в этой статье, будут работать с Gradle версией не ниже 2.10. Для этого в Android Studio выбрать параметр Use default Gradle wrapper и подправить distributionUrl в файле gradle-wrapper.properties.

Рис. 3. Настройка Gradle в Android Studio
Рис. 3. Настройка Gradle в Android Studio

В случае если приложение не проходит регистрацию в системе, от Google приходит одна-единственная ошибка service_not_available. В первую очередь нужно посмотреть, корректно ли при регистрации на сайте были заданы имя приложения и название пакета. У меня такая ошибка возникала из-за нестабильного интернет-соединения, на Stack Overflow советуют проверить настройки даты и времени, а также разрешения в манифест-файле.

 

Заключение

Push-уведомления — невероятно удобная вещь, которая используется практически в каждом приложении. Такими сообщениями принято напоминать о различных акциях, скидках и новостях, что побуждает пользователя чаще открывать приложение. Как ты смог убедиться, компания Google создала очень удобный API, работать с которым — одно удовольствие. Для лучшего понимания материала скачай с нашего сайта подробные исходники приложения, а также примеры запросов с помощью cURL. Если останутся вопросы — обязательно пиши мне на почту. Удачи!

Оставить мнение