Learn more about implementing GCM support on an Android client.
There have already been written about GCM. What is this article for?
That's right, they wrote. Literally this week, an article was published on Habr’s GCM - a new service of Push notifications from Google (if you are not familiar with Google Cloud Messaging for Android, I advise you to read it before reading this article, especially since my article does not describe the process of creating a project with GCM). I don’t know if its author GCM used it in a real application or not, but I had to. That's why I want to describe something that did not find a place in the previous article, or that was not explained. To add all this with a comment to the previous article, I'm afraid an impossible task.
Required Permissions
Everything is clear here, we don’t need GCM without access to the Internet.<uses-permissionandroid:name="android.permission.INTERNET" />GCM requires access to a Google account<uses-permissionandroid:name="android.permission.GET_ACCOUNTS" />There was even a dispute about this in the last topic, but none of the participants decided to look at the source code. The documentation is silent about this moment, and only says that you might want to capture it<uses-permissionandroid:name="android.permission.WAKE_LOCK"/>PowerManager.WakeLock. So, if you use the standard GCM library, then you will have to add such permission.
In short, the mechanism of work is this: our application subscribes to receive broadcast requests. Upon receipt of the request, we set the received Intent to the class name (setClassName()) in the name of our expanding serviceGCMBaseIntentService, then capture itWakeLockwith the flagPowerManager.PARTIAL_WAKE_LOCK(we do not let the CPU sleep, the screen and so on sleeps quietly), start Intent as a service, release it after exiting theonHandleIntentserviceWakeLock.
They did not believe and did not add this permission, and in the end we get this exception:java.lang.SecurityException: Neitheruser 10110 norcurrentprocesshasandroid.permission.WAKE_LOCK.We create our own permission and request it ourselves. We do this so that no one except us can receive our messages.<permissionandroid:name="{имя пакета приложения}.permission.C2D_MESSAGE"android:protectionLevel="signature" /><uses-permissionandroid:name="{имя пакета приложения}.permission.C2D_MESSAGE" />
Note: if you set itminSdkVersionto16or above (Jelly Bean and subsequent versions), then you do not need this permission (after 2 years, I hope it can be omitted).Actually permission to register with GCM and receive messages.<uses-permissionandroid:name="com.google.android.c2dm.permission.RECEIVE" />
Is the registration code (registationId) changing?
Consider the code from the sample application:
final String regId = GCMRegistrar.getRegistrationId(this);
if (regId.equals("")) {
// Automatically registers application on startup.
GCMRegistrar.register(this, SENDER_ID);
}It seems that there are no other conditions. So, does not change? If you click on this link: http://developer.android.com/intl/en/guide/google/gcm/adv.html#reg-state , you can find out what can still change. There are two such cases:
- Program update
- Creating a backup and restoring from it
To check for program updates, I wrote a small helper class. Maybe someone will come in handy:
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.preference.PreferenceManager;
publicfinalclassApplicationVersionHelper{
publicstaticfinal String APP_VERSION_PREFS = "application_version";
publicstaticbooleanisApplicationVersionCodeEqualsSavedApplicationVersionCode(Context context){
return getApplicationVersionCode(context) == getApplicationVersionCodeFromPreferences(context);
}
publicstaticintgetApplicationVersionCode(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;
}
publicstaticintgetApplicationVersionCodeFromPreferences(Context context){
return context.getSharedPreferences(APP_VERSION_PREFS, Context.MODE_PRIVATE).getInt("application_version_code", 0);
}
publicstaticvoidputCurrentPackageVersionInPreferences(Context context){
context.getSharedPreferences(APP_VERSION_PREFS, Context.MODE_PRIVATE).edit().putInt("application_version_code", getApplicationVersionCode(context)).commit();
}
}Please note that the settings are obtained not through
PreferenceManager.getDefaultSharedPreferences, but through the named settings file. Why this is done, I will explain later. Now we need to call
putCurrentPackageVersionInPreferencesafter successful registration in GCM and on our service, and the registration verification code turns into:final String regId = GCMRegistrar.getRegistrationId(this);
if (regId.equals("") || !isApplicationVersionCodeEqualsSavedApplicationVersionCode(this)) {
// Automatically registers application on startup.
GCMRegistrar.register(this, SENDER_ID);
}To handle the creation of a backup (not everyone knows about this feature at all. If it became interesting, then read here - http://developer.android.com/intl/en/guide/topics/data/backup.html ) I propose the following solution : just do not save the settings with the name from the constant
ApplicationVersionHelper.APP_VERSION_PREFSwhen backing up. So the named settings file came in handy :) Then it isApplicationVersionCodeEqualsSavedApplicationVersionCodewill return falseduring data recovery and we will send a registration request.Handlers in the GCMIntentService
In
GCMIntentService(a class inherited from GCMBaseIntentService) we have to redefine several methods. Briefly on them:
this method is called after successful registration in GCM, from here we need to transferprotectedvoidonRegistered(Context context, String registrationId)registrationIdto our serverthis method is called after successful deregistration in GCM, we also transfer itprotectedvoidonUnregistered(Context context, String registrationId)registrationIdto our server for exclusion from the mailing list (many applications will never use this feature)receiving a message from GCM, if there is a payload, then the data is inprotectedvoidonMessage(Context context, Intent intent)intentreceive notification from GCM about deleted messages, what it is and what it is with, see here - http://developer.android.com/intl/en/guide/google/gcm/adv.html#payloadprotectedvoidonDeletedMessages(Context context, int total)irreparable error while receiving data inpublicvoidonError(Context context, String errorId)errorIderror coderecoverable error while receiving data inprotectedbooleanonRecoverableError(Context context, String errorId)errorIderror code. If we returntrue, then let us make another attempt, iffalse, then we will stop the attempts. I recommend returning in this methodsuper.onRecoverableError(context, errorId);
We clean for ourselves!
Remember to cancel the registration process, if it is running, and call GCMRegistrar.onDestroy in the onDestroy method of your main Activity. Here's how I did it:
@OverrideprotectedvoidonDestroy(){
if (registerTask != null)
{
registerTask.cancel(true);
}
try
{
CMRegistrar.onDestroy(this);
}
catch(Exception ignored)
{
}
super.onDestroy();
}registerTaskhere is the asynchronous job ( AsyncTask).Conclusion
I advise you to read http://developer.android.com/intl/en/guide/google/gcm/index.html (and there are 5 points) before using GCM in your application, and if you have questions (what about WAKE_LOCK permission), then Do not be afraid to get into the source code.