GCM (Google Cloud Messaging) を試してみる

GCM (Google Cloud Messaging) は、Googleのサーバ (GCMサーバ) を介してAndroid機にキー・バリュー形式のメッセージを送るサービスです。最近出た「Google Androidプログラミング入門 改訂2版」の付録としてGCMが取り上げられていたのもあって、この本のサンプルに沿って (というかほぼ丸写し) GCMによるメッセージ送受信を試してみました。

テスト環境: GCM受信にはGoogle Playアプリが必須 (らしい) なので、Androidエミュレータでは動作しません。

Google Androidプログラミング入門 改訂2版

Google Androidプログラミング入門 改訂2版

Google APIs上でのプロジェクト作成

GCMでメッセージを送るには、事前にGoogle APIs Console上でプロジェクトを作成し、GCMサービスを有効にしておく必要があります。

Google APIs Consoleで新規にプロジェクトを作成し、Servicesを選択すると利用可能なサービス一覧が表示され、ここで "Google Cloud Messaging for Android" をONにします。

さらに、API Accessから "Create new Server key" を選択することで、GCM送信時の認証に必要なAPIキーが発行されます。

ここで表示されるAPIキーと、URLの一部として表示されるプロジェクトID (Sender ID) はあとで使うのでメモっておきます。

クライアントアプリの構成

GCMを受信するAndroidアプリを開発するためには、Android SDK Managerから、"Google Cloud Messaging for Android Library" をダウンロードしておく必要があります。これをダウンロードすると、Android SDKをインストールしたディレクトリの下に extras/google/gcm/gcm-client/dist/gcm.jar というjarファイルができるので、これをEclipseAndroidアプリケーションプロジェクトの libs/ にコピーしておきます (プロジェクトのプロパティとして、外部jarファイルを指定する方法もありますが、今回試した限りではうまく動きませんでした…)。

最小構成としては、クライアントアプリは以下の2つから構成されます。

  • Registration ID (メッセージを送る側から送信先を指定するためのID) をGCMサーバから取得するアクティビティ
  • GCMサーバからのメッセージ受信を処理するサービス

これらのアクティビティとサービス、およびGCM受信に必要な各種のパーミッションをAndroidManifest.xmlに記述する必要があります。

AndroidManifest

GCM受信のために必要になる一連のパーミッションを記述します。その中に、<アプリケーションのパッケージ名>.permission.C2D_MESSAGE というユーザ定義パーミッションが含まれるので、その定義も記述する必要があります。

<manifest ...>
    ....
    <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.gcmdemo.permission.C2D_MESSAGE" />
    <uses-permission android:name="com.example.gcmdemo.permission.C2D_MESSAGE"/>
    ....

さらに、GCMのメッセージ受信時に発行されるブロードキャストインテントを受信するBroadcastReceiverと、メッセージ受信を処理するサービスのクラス名を記述する必要があります。

<manifest ...>
    ...
    <application ...>
        // Registration IDを取得するアクティビティ
        <activity android:name=".GCMDemo" ... />

        // GCM受信時のブロードキャストを処理するレシーバ
        <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" />
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="com.example.gcmdemo" />
            </intent-filter>
        </receiver>

        // GCMサーバからのメッセージ受信を処理するサービス 
        <service android:name=".GCMIntentService" />
        ...

Registration IDの取得

アクティビティ (GCMDemo) では、Register/Unregisterの2つのボタンにonClick()を定義して、GCMサーバへの登録/登録解除を行います。

ここで主に使うのがgcm.jarに含まれる com.google.android.gcm.GCMRegistrar というクラスで、実際の登録/登録解除処理はこのクラスが一手に引き受けています。

    GCMRegistrar.register(this, SENDER_ID); // 登録

    GCMRegistrar.unregister(this); // 登録解除

ここで、SENDER_IDには、Google APIs Consoleで取得したSender IDの値を指定します。

登録に成功するとGCMサーバからRegistration IDが発行されて、この値をメッセージ送信時に指定することになりますが、Registration IDの取得は後述の「受信メッセージの処理」として記述することになります。

受信メッセージの処理

com.google.android.gcm.GCMBaseIntentService のサブクラスとして、GCM関連で発生するイベントの処理を記述します (ここでは GCMIntentService というクラス名)。

  • onRegistered(): GCMサーバへの登録が完了したときに発生 (前述のGCMRegistrar.register() に対応)
  • onUnregistered(): GCMサーバへの登録解除が完了したときに発生 (前述のGCMRegistrar.unregister() に対応)
  • onMessage(): メッセージ受信時に発生
  • onError(): 何らかのエラーが発生したときに発生

onRegistered() では、発行されたRegistration IDをアプリケーションサーバに通知する処理が期待されていますが、今回はこの処理ははしょって、LogCatに表示したRegistration IDをコピペして使うことにします。

onMessage() では、NotificationManagerを使用してメッセージの到着を表示します。

public class GCMIntentService extends GCMBaseIntentService {
    ....
    private static String TAG = "gcm_demo_service";

    // GCMサーバへの登録完了時の処理
    protected void onRegistered(Context context, String registrationId) {
        // 取得したRegistration IDの値をログ出力
        Log.d(TAG, "GCMIntentService.onRegistered registrationId=" + registrationId);
        sendRegistrationIdToAppServer(registrationId);
    }
    static void sendRegistrationIdToAppServer(String registrationId) {
        // アプリケーションサーバにregistration IDを送る処理。今回は何もしない。
    }

    // メッセージ到着時の処理
    protected void onMessage(Context context, Intent intent) {
        Bundle messageBundle = intent.getExtras(); // Intent#getExtras() で受信したメッセージの内容を取得

        // キー・バリュー形式のメッセージにBundleとしてアクセス
        Iterator<String> iterator = messageBundle.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            // 取得したキー・バリューの対をログ出力
            Log.d(TAG, key + " = " + messageBundle.getString(key));
        }
        try {
            showNotification(context,
                    URLDecoder.decode(messageBundle.getString("message"), "UTF-8"),
                    URLDecoder.decode(messageBundle.getString("detail"), "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
    private void showNotification(Context context, String message, String detail) {
        // NotificationManagerを使って、受信したメッセージの内容を表示
    }
    ...

PCとAndroid機をUSBKケーブルで接続した状態で、このアプリケーションを起動してRegisterボタンを押すと、LogCatにRegistration IDが出力されていることを確認できます。

ログ中に "internal error: retry receiver class not set yet" なるエラーが出ているのが気になりますが、調べてみた限りでは無視して良さそうなので気にしないことにします。
retry receiver class not set yet ? - Google Groups

# 今回テキストとして使った「Google Androidプログラミング入門」のサンプルプログラムでは、Notification周りのコードにdeprecatedなAPIが使われているようなので、warningが出ないように書き換えたいところ

メッセージの送信

Androidアプリ側は一応完成ということで、メッセージを送るサーバ側のプログラムを作ります。ちょっとしたフォームでメッセージの内容を入力したいところですが、今回は最小限の動作を確認するということで、決め打ちのメッセージを送ってみます。
言語は重要ではありませんが、勝手が分かっているRubyを選択。

require 'json'
require 'net/https'

# GCMサーバの接続先 (https://android.googleapis.com/gcm/send)
GCM_HOST = "android.googleapis.com"
GCM_PATH = "/gcm/send"

REG_ID = # Registration ID (Androidアプリ実行時にGCMサーバから発行されたもの)
API_KEY = # APIキー (Google APIs Consoleで発行されたもの)

// 送信するメッセージの内容
message = {
  "registration_ids" => [REG_ID],
  "collapse_key" => "collapse_key",
  "delay_while_idle" => false,
  "time_to_live" => 60,
  "data" => { "message" => "GCM Demo",
              "detail" => "Hello world"}
}

// HTTPS POST実行
http = Net::HTTP.new(GCM_HOST, 443);
http.use_ssl = true
http.start{|w|
  response = w.post(GCM_PATH,
    message.to_json + "\n",
    {"Content-Type" => "application/json",
     "Authorization" => "key=#{API_KEY}"})
  puts "response code = #{response.code}"
  puts "response body = #{response.body}"
}

PC上でこれを実行すると、Android機側では以下のような通知が現れます。

ついでにLogCatを見ると、以下のようにメッセージの内容が取得できていることを確認できます。