Тут уже писали об GCM. Для чего эта статья?
Верно, писали. Буквально на этой неделе на Хабре была опубликована статья GCM – новый сервис Push-уведомлений от Google (если вы еще не знакомы с Google Cloud Messaging for Android, то советую прочитать её перед прочтением этой статьи, тем более в моей статье не описываются процесс создания проекта с GCM). Не знаю использовал её автор GCM в реальном приложении или нет, а вот мне пришлось. Поэтому-то я и хочу описать кое-что, чему не нашлось места в предыдущей статье, или что не было объяснено. Добавить это все комментарием в предыдущую статью, боюсь, невыполнимая задача.
Необходимые разрешения
-
<uses-permission android:name="android.permission.INTERNET" />
Тут всё ясно, без доступа к интернету GCM нам и не нужен
-
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
GCM требует доступ к Google-аккаунту
<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, надеюсь, можно будет опускать). -
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
Собственно разрешение на регистрацию в GCM и получение сообщений.
Изменяется ли код регистрации (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 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", getPackageVersion(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
)
нам предстоит переопределить несколько методов. Кратко по ним:
-
protected void onRegistered(Context context, String registrationId)
этот метод вызывается после успешной регистрации в GCM, отсюда нам нужно передать
registrationId
на наш сервер -
protected void onUnregistered(Context context, String registrationId)
этот метод вызывается после успешной отмены регистрации в GCM, так же передаем
registrationId
на наш сервер для исключения из рассылки (многие приложения никогда не будут пользоваться данной возможностью) -
protected void onMessage(Context context, Intent intent)
получение сообщения от GCM, если есть полезная нагрузка (payload), то данные лежат в
intent
-
protected void onDeletedMessages(Context context, int total)
получение уведомления от GCM об удаленных сообщениях, что это такое и с чем их есть смотрите здесь — http://developer.android.com/intl/ru/guide/google/gcm/adv.html#payload
-
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 разрешения), то не бояться залезть в исходных код.
Автор: 4ex