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


  • <uses-permissionandroid:name="android.permission.INTERNET" />
    Everything is clear here, we don’t need GCM without access to the Internet.
  • <uses-permissionandroid:name="android.permission.GET_ACCOUNTS" />
    GCM requires access to a Google account
    <uses-permissionandroid:name="android.permission.WAKE_LOCK"/>
    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 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 service GCMBaseIntentService, then capture it WakeLockwith the flag PowerManager.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 the onHandleIntentservice WakeLock.

    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.

  • <permissionandroid:name="{имя пакета приложения}.permission.C2D_MESSAGE"android:protectionLevel="signature" /><uses-permissionandroid:name="{имя пакета приложения}.permission.C2D_MESSAGE" />
    We create our own permission and request it ourselves. We do this so that no one except us can receive our messages.

    Note: if you set it minSdkVersionto 16or above (Jelly Bean and subsequent versions), then you do not need this permission (after 2 years, I hope it can be omitted).
  • <uses-permissionandroid:name="com.google.android.c2dm.permission.RECEIVE" />
    Actually permission to register with GCM and receive messages.


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:
  1. Program update
  2. 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:
  • protectedvoidonRegistered(Context context, String registrationId)
    this method is called after successful registration in GCM, from here we need to transfer registrationIdto our server
  • protectedvoidonUnregistered(Context context, String registrationId)
    this method is called after successful deregistration in GCM, we also transfer it registrationIdto our server for exclusion from the mailing list (many applications will never use this feature)
  • protectedvoidonMessage(Context context, Intent intent)
    receiving a message from GCM, if there is a payload, then the data is in intent
  • protectedvoidonDeletedMessages(Context context, int total)
    receive 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#payload
  • publicvoidonError(Context context, String errorId)
    irreparable error while receiving data in errorIderror code
  • protectedbooleanonRecoverableError(Context context, String errorId)
    recoverable error while receiving data in errorIderror code. If we return true, then let us make another attempt, if false, 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.

Also popular now: