Тут уже писали об GCM. Для чего эта статья?
Верно, писали. Буквально на этой неделе на Хабре была опубликована статья GCM – новый сервис Push-уведомлений от Google (если вы еще не знакомы с Google Cloud Messaging for Android, то советую прочитать её перед прочтением этой статьи, тем более в моей статье не описываются процесс создания проекта с GCM). Не знаю использовал её автор GCM в реальном приложении или нет, а вот мне пришлось. Поэтому-то я и хочу описать кое-что, чему не нашлось места в предыдущей статье, или что не было объяснено. Добавить это все комментарием в предыдущую статью, боюсь, невыполнимая задача.
Необходимые разрешения
Тут всё ясно, без доступа к интернету GCM нам и не нужен<uses-permission android:name="android.permission.INTERNET" />
GCM требует доступ к Google-аккаунту<uses-permission android:name="android.permission.GET_ACCOUNTS" />
По этому поводу в прошлой теме даже был спор, но никто из участников не решил посмотреть в исходных код. Документация этот момент умалчивает, и лишь говорит, что возможно вы захотите захватить<uses-permission android:name="android.permission.WAKE_LOCK"/>
PowerManager.WakeLock
. Так вот, если вы пользуетесь стандартной библиотекой GCM, то вам придется добавлять такое разрешение.
Вкратце механизм работы такой: наше приложение подписывается на получение широковещательных запросов. При получении запроса мы устанавливаем полученному Intent'у имя класса (setClassName()
) в имя нашего сервиса расширяющегоGCMBaseIntentService
, затем захватываемWakeLock
с флагомPowerManager.PARTIAL_WAKE_LOCK
(не даем уснуть только CPU, экран и прочее спит спокойно), запускаем Intent как сервис, по выходу изonHandleIntent
сервиса освобождаемWakeLock
.
Не поверили и не стали добавлять это разрешение, и в итоге получаем вот такое исключение:
java.lang.SecurityException: Neither user 10110 nor current process has android.permission.WAKE_LOCK.
Создаем свое собственное разрешение и сами его запрашиваем. Это мы делаем для того, чтобы никто кроме нас не смог получать наши сообщения.<permission android:name="{имя пакета приложения}.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="{имя пакета приложения}.permission.C2D_MESSAGE" />
Примечание: если вы выставилиminSdkVersion
в16
или выше (Jelly Bean и последующие версии), то это разрешение вам не нужно (года через 2, надеюсь, можно будет опускать).
Собственно разрешение на регистрацию в GCM и получение сообщений.<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
Изменяется ли код регистрации (registationId)?
Рассмотрим код из приложения-примера:
final String regId = GCMRegistrar.getRegistrationId(this);
if (regId.equals("")) {
// Automatically registers application on startup.
GCMRegistrar.register(this, SENDER_ID);
}
Вроде бы других условий нет. Так что, не изменяется? Если перейти по этой ссылке: http://developer.android.com/intl/ru/guide/google/gcm/adv.html#reg-state, можно узнать что все-таки может измениться. Таких случая два:
- Обновление программы
- Создание резервной копии и восстановление из неё
Для проверки на обновление программы я написал небольшой класс-помощник. Может быть кому-нибудь пригодится:
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.preference.PreferenceManager;
public final class ApplicationVersionHelper
{
public static final String APP_VERSION_PREFS = "application_version";
public static boolean isApplicationVersionCodeEqualsSavedApplicationVersionCode(Context context)
{
return getApplicationVersionCode(context) == getApplicationVersionCodeFromPreferences(context);
}
public static int getApplicationVersionCode(Context context)
{
PackageManager pm = context.getPackageManager();
PackageInfo packageInfo;
int applicationVersion = 1;
try
{
packageInfo = pm.getPackageInfo(context.getPackageName(), 0);
applicationVersion = packageInfo.versionCode;
}
catch (NameNotFoundException ignored)
{
}
return applicationVersion;
}
public static int getApplicationVersionCodeFromPreferences(Context context)
{
return context.getSharedPreferences(APP_VERSION_PREFS, Context.MODE_PRIVATE).getInt("application_version_code", 0);
}
public static void putCurrentPackageVersionInPreferences(Context context)
{
context.getSharedPreferences(APP_VERSION_PREFS, Context.MODE_PRIVATE).edit().putInt("application_version_code", getApplicationVersionCode(context)).commit();
}
}
Обратите внимание на то, что настройки получаются не через
PreferenceManager.getDefaultSharedPreferences
, а через именованный файл настроек. Для чего это делается, я объясню позже.Теперь нам нужно вызвать
putCurrentPackageVersionInPreferences
после успешной регистрации в GCM и на нашем сервисе, а код проверки регистрации превращается в:final String regId = GCMRegistrar.getRegistrationId(this);
if (regId.equals("") || !isApplicationVersionCodeEqualsSavedApplicationVersionCode(this)) {
// Automatically registers application on startup.
GCMRegistrar.register(this, SENDER_ID);
}
Для обработки создания резервной копии (не все об этой возможности вообще знают. Если стало интересно, то читать здесь — http://developer.android.com/intl/ru/guide/topics/data/backup.html) я предлагаю следующее решение: просто не сохранять настройки с именем из константы
ApplicationVersionHelper.APP_VERSION_PREFS
при бэкапе. Вот и пригодился именованный файл настроек :) Тогда isApplicationVersionCodeEqualsSavedApplicationVersionCode
вернет false
при восстановлении данных и мы отправим запрос на регистрацию.Обработчики в GCMIntentService
В
GCMIntentService
(классе унаследованном от GCMBaseIntentService
)нам предстоит переопределить несколько методов. Кратко по ним:
этот метод вызывается после успешной регистрации в GCM, отсюда нам нужно передатьprotected void onRegistered(Context context, String registrationId)
registrationId
на наш сервер
этот метод вызывается после успешной отмены регистрации в GCM, так же передаемprotected void onUnregistered(Context context, String registrationId)
registrationId
на наш сервер для исключения из рассылки (многие приложения никогда не будут пользоваться данной возможностью)
получение сообщения от GCM, если есть полезная нагрузка (payload), то данные лежат вprotected void onMessage(Context context, Intent intent)
intent
получение уведомления от GCM об удаленных сообщениях, что это такое и с чем их есть смотрите здесь — http://developer.android.com/intl/ru/guide/google/gcm/adv.html#payloadprotected void onDeletedMessages(Context context, int total)
невосстановимая ошибка при получении данных, вpublic void onError(Context context, String errorId)
errorId
код ошибки
восстановимая ошибка при получении данных, вprotected boolean onRecoverableError(Context context, String errorId)
errorId
код ошибки. Если вернемtrue
, то разрешим совершить еще одну попытку, еслиfalse
, то прекратим попытки. Я рекомендую в этом методе возвращатьsuper.onRecoverableError(context, errorId);
Чистим за собой!
Не забудьте отменить процесс регистрации, если он запущен, и вызвать GCMRegistrar.onDestroy в методе onDestroy вашей главной Activity. Вот как это сделано у меня:
@Override
protected void onDestroy()
{
if (registerTask != null)
{
registerTask.cancel(true);
}
try
{
CMRegistrar.onDestroy(this);
}
catch(Exception ignored)
{
}
super.onDestroy();
}
registerTask
тут — асинхронное задание (AsyncTask
).Заключение
Советую прочитать http://developer.android.com/intl/ru/guide/google/gcm/index.html (а там 5 пунктов) перед использованием GCM в своем приложении, а если есть вопросы (как насчет WAKE_LOCK разрешения), то не бояться залезть в исходных код.