Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Using Google Cloud Messaging (GCM) "Push Messaging" to Alert Your App to Changes

Author: Ian Darwin
Published? false
FormatLanguage: AsciiDoc

Problem:

You want to get "push" notifications sent asynchronously from a server, without setting you your own complex infrastructure. This can be used to send short data (up to about 4KB), or to send a "ping" notification which will cause the app to download new data from your server.

Solution:

Consider using Google Cloud Messaging (GCM)

Discussion:

GCM is a free service offered to Android developers to deliver small messages direct to your application running on an Android device. This avoids your application having to poll a server, which would either be not very responsive, or very bad for battery life.

The basic operation of GCM is: # The app reisters with GCM to recieve messages; # Your server detects some condition that requires notifying the particular user's device (eg., new or changed data available) You send a message to the GCM server. # The GCM server sends the message to your user's device, where it is passed to your app at a BroadcastReceiver # You do something with the information.

There are other solutions, such as intercepting incoming SMS (see recipe [[2017]]. GCM has the advantage that it's free, and the disadvantage that it takes a bit longer to set up. Note that prior to API 4.0.4, the user was required to have a Google sign-in in order to receive GCM push messages.

The basic steps are: # Sign up with Google to use GCM # Configure your client's AndroidManifest.xml # Create a BroadcastReceiver to handle the incoming notifications # ... # Configure your back-end server to notify the GCM server when it has data to send (or to send a notice to tell the client to download new data, a form of distributed MVC).

Sign up with Google to use GCM

Assume that you have a Google developer account (if not, see "Signing up" at [[600]]). Go to your Developer Console at https://cloud.google.com/console/project. If this is your first time here, or you need to make a different project, click Create Project, otherwise, select the project. In either case, note down the Project Number, which appears in the URL and at the top of the page. This same number is used as your GCM Sender Id.

At the left of the page, select APIs.

Click "Google Cloud Messaging for Android" to ON. You have to accept a license. The API will disappear from the list and reappear at the top, with status set ON

Back at the left, under APIs Auth, click Credentials, then Create New Key (not New Client Id). Select Server Key (not Android Key). Click Create. Put in your Server's IP (as many as you need). Click OK.

Ther reason you need a Server key is that your app server will be the one contacting GCM, not your client app.

Save the API key that is generated; you will need it in your server..

This is further described in http://developer.android.com/google/gcm/gs.html.

Set Up For Developing Your Client App

Ensure you have the Google Play Services SDK installed (use the Android SDK Manager in your IDE, or the android sdk command-line tool. Then select Google Play Services (under Extras).

If this is your first use of Google Play Services, you'll have to install the Library Project from /extras/google/googleplayservices/libproject/google-play-serviceslib/ to a source folder; if using Eclipse, then import it using File->Import->Android->Existing Android Code. If like me you prefer to keep everything in your workspace, you can point the Import at the library project path above, but be sure to check the checkbox "Copy Files Into Workspace".

Then you need to make your client app project depend upon this library using Project->Properties->Android->Library->Add (it is a common mistake to use Project->Build Path->Add Library->Project; this does not work).

Configure your client's AndroidManifest.xml

There are several pieces to go in your AndroidManifest.

Add the permissions android.permission.INTERNET and com.google.android.c2dm.permission.RECEIVE Also add android.permission.WAKELOCK if you want to keep the device from sleeping between recipt of a message and its processing. Also add android.permission.GETACCOUNTS if the device API is lower than 4.0.4.

You also have to build your own permission, to prevent other apps from stealing your messages. Create and give yourself the permission applicationPackage .permission.C2DMESSAGE (e.g., com.example.gcmplay.permission.C2DMESSAGE). You must use exactly this name for your custom permission. This might look like the following:



Inside the element, add:


Configure a BroadcastReceiver to receive the GCM intent, protected by the GCM permission. This might look like the following:


    
        
        
    

Last but not least, you'll probably want an IntentService to receive the messages from the Recievier and get them into the app:


Configure ProGuard to preserve GCM Services in your APK

If you are using ProGuard (see [[2111]]), add the following to your progaurd-project.txt file
-keep class * extends java.util.ListResourceBundle {
    protected Object[][] getContents();
}

-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable { public static final *** NULL; }

-keepnames @com.google.android.gms.common.annotation.KeepName class * -keepclassmembernames class * { @com.google.android.gms.common.annotation.KeepName *; }

-keepnames class * implements android.os.Parcelable { public static final ** CREATOR; }

Client Startup Code

In your app's startup code (e.g., in onCreate() or onResume()), check that Google Play Services are available, with the static method GooglePlayservicesUtil.isGooglePlayServicesAvailable(Context ctx), e.g.,

boolean checkForGcm() {
    int ret = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
    if (ConnectionResult.SUCCESS == ret) {
        return true;
    } else {
        if (GooglePlayServicesUtil.isUserRecoverableError(ret)) {
            GooglePlayServicesUtil.getErrorDialog(ret, this,
                    PLAY_SERVICES_RESOLUTION_REQUEST).show();
        } else {
            Toast.makeText(this,
                    "Google Message Not Supported on this device",
                    Toast.LENGTH_LONG).show();
        }
        return false;
    }
}

At this point you have to decide whether you are going to use HTTP or XMPP to communicate from the server to your client. XMPP (a chat protocol now used by Google Talk) allows bidirectional messages, whereas HTTP is simpler to set up but is only one way. While the official documentation uses XMPP, we'll use HTTP because it is simpler; you can later refer to the official documentation if you want to use XMPP.

Create a BroadcastReceiver to handle the incoming notification

The Broadcast Receiver gets the message via an Intent, and hands it off to another class (the IntentService) to handle it. The only change it makes to the incoming Intent it to change it to explicitly have the class name of the Service in order to pass it along. Reusing the Intent this was causes the Intent.extra - which contains the actual data from the server - to be passed along.

The use of WakefulBroadcastReceiver is optional; if you don't care about the device possibly going to sleep before the Service has finished, you can just use a plain BroadcastReceiver (and remove the call to completeWakefulIntent in the service).

public class GcmReceiver extends WakefulBroadcastReceiver {
    /** 
     * Called when a message is received from GCM for this App
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(GcmMainActivity.TAG, "GcmReceiver.onReceive()");

// Recycle "intent" into an explicit intent for the handler ComponentName comp = new ComponentName(context.getPackageName(), GcmService.class.getName()); intent.setComponent(comp);

// Pass control to the handler; using "startWakefulService" will keep the device awake so the user // has a good chance of seeing the message; the WakeLock is released at the end of the handler. // Re-using the incoming intent this way lets us pass along Intent.extras etc. startWakefulService(context, intent); // If we didn't throw an exception yet, life is good. setResultCode(Activity.RESULT_OK); } }

The list bit of client code is the IntentService also known as the "do what you want with the result" section. This trivial example merely displays the result in the logcat output, but that is enough to show that our "Hello World" application is receiving messages and handling them. A better example would show the result in a Notification; an even better example would update the GUI in the main Activity. These are left as an exercise for the reader.

/** Intent Service to handle each incoming GCM message */
public class GcmService extends IntentService {
    
    final static String TAG = GcmMainActivity.TAG;

public GcmService() { super(GcmService.class.getSimpleName()); }

@Override protected void onHandleIntent(Intent intent) { String messageType = GoogleCloudMessaging.getInstance(this).getMessageType(intent); Log.d(GcmMainActivity.TAG, "Got a message of type " + messageType); Bundle extras = intent.getExtras(); if (messageType.equals(MESSAGE_TYPE_MESSAGE)) { // GOOD String message = extras.getString("message"); Log.d(TAG, "MESSAGE = '" + message + "' (" + extras.toString() + ")"); } else if (messageType.equals(MESSAGE_TYPE_SEND_ERROR)) { Log.e(TAG, "Error sending previous message " + "(which is odd because we don't send any"); } else if (messageType.equals(MESSAGE_TYPE_DELETED)) { // Too many messages for you, server deleted some } GcmReceiver.completeWakefulIntent(intent); } }

Configure your back-end server to notify the GCM server when it has data

Instead of writing a full server, here we just show a standalone main program containing the code that your server would use to send a message to the client. It just uses an Java HttpUrlConnection to talk to the Google server.

In real life your app would need to send its "Registration ID" string to your server, which would use it as a token to identify the client to recieve this particular message. The Registration Id is a unique identifier for a version of your app installed at a particular instant on a particular device; uninstall and reinstall the same app and you get a different client id.

Your server also needs the API key we generated near the outset of this recipe in order to authenticate itself. Keep this key confidential as it would allow anybody who finds it to send messages to your clients.

Here is the code from my GcmMockServer:

/**
 * A very simple program which pretends to be a "server" in that it sends
 * a notification to the Google Cloud Messaging Server to cause it to send
 * a message to our GCM Client.
 * @author Ian Darwin, http://androidcookbook.com/
 */
public class GcmMockServer {

/** Confidential Server API key gotten from the Google Dev Console -> * Credentials -> Public API Access -> Key for Android Apps */ final static String AUTH_KEY; // set in a static initializer, not shown final static String POST_URL = "https://android.googleapis.com/gcm/send"; public static void main(String[] args) throws Exception {

final String[][] MESSAGE_HEADERS = { {"Content-Type", "application/json"}, { "Authorization", "key=" + AUTH_KEY} };

String regIdFromClientApp = null; // has to be set somehow! String jsonMessage = "{\n" + " \"registration_ids\" : [\""+ regIdFromClientApp + "\"],\n" + " \"data\" : {\n" + " \"message\": \"See your doctor ASAP!\"\n" + " }\n" + "}\n"; // Dump out the HTTP send for debugging for (String[] hed : MESSAGE_HEADERS) { System.out.println(hed[0] + "=>" + hed[1]); } System.out.println(jsonMessage); // Actually send it. sendMessage(POST_URL, MESSAGE_HEADERS, jsonMessage); }

private static void sendMessage(String postUrl, String[][] messageHeaders, String jsonMessage) throws IOException { HttpURLConnection conn = (HttpURLConnection) new URL(postUrl).openConnection(); for (String[] h : messageHeaders) { conn.setRequestProperty(h[0], h[1]); } System.out.println("Connected to " + postUrl); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // ensure response always from server

PrintWriter pw = new PrintWriter( new OutputStreamWriter(conn.getOutputStream())); pw.print(jsonMessage); pw.close(); System.out.println("Connection status code " + conn.getResponseCode()); } }

So, does it all work? If everything has been set up just so, and you run the client in a device (or maybe it will work in an emulator), and then you copy the RegistrationId string into the server (logcat is your friend here!), and then you run the GcmMockServer as a Java Application, and the winds are blowing from the south, then you will see the following, or something very like it, in the logcat output:

D/com.darwinsys.gcmdemo( 7496): GcmReceiver.onReceive()

D/com.darwinsys.gcmdemo( 7496): Got a message of type gcm

D/com.darwinsys.gcmdemo( 7496): MESSAGE = 'See your doctor ASAP!' (Bundle[{from=117558675814, message=See your doctor ASAP!, android.support.content.wakelockid=2, collapse_key=do_not_collapse}])

See Also:

For anything we may have missed, see the official documentation at http://developer.android.com/google/gcm/index.html.

The client is in the GcmClient directory of the repository; the mock server is in GsmMockServer there too.

Download:

The source code for this project is in the Android Cookbook repository, http://github.com/IanDarwin/Android-Cookbook-Examples/,in the subdirectory GcmClient.
No records found.