I was forced to use GCM, so here are my notes. GCM is a mechanism/service for sending push notifications from a server to Android apps. It was made public on June 26, 2012. Before that, it was implemented using C2DM. This is a simple sample, but I’ll keep it as a memo.
It’s available here.
Partially quoted from http://developer.android.com/google/gcm/index.html and http://www.kotemaru.org/2013/07/28/android-push-message.html.
Mechanism
- Let’s call Google’s server the GCM server
- Let’s call our own server the self-server
- First, obtain the device ID (registration ID) from the GCM server.
- Send the registration ID to the self-server.
- From the self-server, set the registration ID and send a message to the GCM server.
- The GCM server sends the message to each device, and a push notification is sent.
Preparation
First, obtain the Google API from https://code.google.com/apis/console/.
- From the Service list, turn the status of Google Cloud Messaging for Android to on.
- From API ACCESS, select Create new Server Key to obtain an API Key.
Here, check two pieces of information:
- API Key: Used when sending a message from the self-server to the GCM server. It’s written in Key for server apps. Something like “Jfdsah82…”.
- Sender id: Used when sending a message from the app to the self-server. It’s the number after project in the URL. Something like “4352…”.
1. Client Implementation
Although old, I implemented it with Google APIs 10. First, install GoogleCloudMessaging for Android Library from Android SDK Manager. Next, add {Android SDK directory}/extras/google/gcm/gcm-client/dist/gcm-src.jar to your project. (You can add it by right-clicking in the src directory => Archive File)
Manifest File
Add permissions to the manifest file as follows. Please add the parts surrounded by BEGIN and END. Change com.example.com to your project’s package name.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.gcm_reciever"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="10"
android:targetSdkVersion="10" />
<!-- BEGIN: for GCM_reciever -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name="com.example.gcm_reciever.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcm_reciever.permission.C2D_MESSAGE" />
<!-- END: for GCM_reciever -->
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.gcm_reciever.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- BEGIN: for GCM_reciever -->
<receiver
android:name="com.google.android.gcm.GCMBroadcastReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.example.gcm_reciever" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION"/>
<category android:name="com.example.gcm_reciever"/>
</intent-filter>
</receiver>
<service android:name=".GCMIntentService" />
<!-- END: for GCM_reciever -->
</application>
</manifest>
MainActivity.java
package com.example.gcm_reciever;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import com.google.android.gcm.GCMRegistrar;
import android.net.Uri;
import android.net.http.AndroidHttpClient;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
public class MainActivity extends Activity {
/* Self-server URL */
public static final String url = "http:/hogehoge.net/closet/gcm.php";
/* sender id */
public static final String senderIds = "xxxxxxxxxx";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GCMRegistrar.checkDevice(this);
GCMRegistrar.checkManifest(this);
/* Obtain the registration ID from the GCM server.
* If already registered, send it to the self-server.
* If not registered, register with the GCM server using GCMResistrar.register().
* (It will be sent to the self-server at the Registered timing)
*/
String regId = GCMRegistrar.getRegistrationId(getApplicationContext());
if(regId.equals("")){
Log.i("GCM", "onCreate call register");
GCMRegistrar.register(getApplicationContext(), senderIds);
}else{
Log.i("GCM", "onCreate call sendMyserver");
sendMyServer(regId);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
protected void onDestroy() {
GCMRegistrar.onDestroy(this);
super.onDestroy();
}
/**
* Send the registration ID to the self-server.
* @param regId Registration ID
*/
static public void sendMyServer(String regId){
HttpClient client = new DefaultHttpClient();
List parameters = new ArrayList();
parameters.add(new BasicNameValuePair("action", "reg"));
parameters.add(new BasicNameValuePair("regId", regId));
String query = URLEncodedUtils.format(parameters, "UTF-8");
HttpGet get = new HttpGet(url + "?" + query);
Log.i("GCM", "sendMyServer: regId="+regId);
try {
HttpResponse response = client.execute(get);
Log.i("GCM", "sending my server success.");
} catch (ClientProtocolException e) {
e.printStackTrace();
Log.i("GCM", "sending my server failed.");
} catch (IOException e) {
e.printStackTrace();
Log.i("GCM", "sending my server failed.");
}
}
}
GCMIntentService.java
package com.example.gcm_reciever;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gcm.GCMBaseIntentService;
import com.example.gcm_reciever.*;
public class GCMIntentService extends GCMBaseIntentService {
private String tag = "GCM";
public GCMIntentService() {
super(MainActivity.senderIds);
}
/**
* Called when the registration ID is registered with the GCM server.
* Here, we also send it to the self-server.
*/
@Override
protected void onRegistered(Context context, String regId) {
Log.i(tag, "onRegistered: regId = " + regId);
MainActivity.sendMyServer(regId);
}
/**
* Called when a message is received from the GCM server.
*/
@Override
protected void onMessage(Context context, Intent intent) {
Bundle extras = intent.getExtras();
String message = extras.getString("message");
Log.i(tag, "onMessage: msg = " + message);
}
@Override
public void onError(Context context, String errorId) {
Log.i(tag, "onError: " + errorId);
}
@Override
protected boolean onRecoverableError(Context context, String errorId) {
Log.i(tag, "onRecoverableError: " + errorId);
return super.onRecoverableError(context, errorId);
}
@Override
protected void onUnregistered(Context context, String registrationId) {
// TODO Auto-generated method stub
}
}
2. Server Implementation
<?php
require_once "HTTP/Request.php";
if(!ISSET($_GET['action']))
exit(0);
if($_GET['action'] == "reg"){
$fp = fopen("id.txt", "w");
fwrite($fp, $_GET['regId']);
fclose($fp);
print('saved');
}else if(($_GET['action']) == "send"){
$fp = fopen("id.txt", "r");
$regid = fgets($fp);
fclose($fp);
$apikey = "AIza....";
$msg = 'posted from gcm';
$rq = new HTTP_Request("https://android.googleapis.com/gcm/send");
$rq->setMethod(HTTP_REQUEST_METHOD_POST);
$rq->addHeader("Authorization", "key=".$apikey);
$rq->addPostData("registration_id", $regid);
$rq->addPostData("collapse_key", "1");
$rq->addPostData("data.message", $msg);
if (!PEAR::isError($rq->sendRequest())) {
print "\n" . $rq->getResponseBody();
} else {
print "\nError has occurred";
}
}
3. Error Codes
Here are some errors I experienced and how to deal with them.
java.lang.IllegalArgumentException: Receiver not registered: com.google.android.gcm.GCMBroadcastReceiver@40673a10
=> When registering with GCM, modify the context like GCMRegistrar.onDestroy(getApplicationContext());.
ACCOUNT_MISSING
=> This occurs if the device is not logged in with a Google account. Particular attention is needed for emulators.