Google Cloud Messaging for Android (GCM)を使ってみた

★2014.3.18追記あり

Android 4.1のAPIが公開されましたね!

お仕事でC2DMを使おうとしていた矢先に、

Android Cloud to Device Messaging (C2DM) is deprecated.

http://developer.android.com/guide/google/gcm/c2dm.html

というわけで、C2DMの進化版、GCMを使ってみました。

基本的な使い方はC2DMと似てるので、移行はそんなに難しくなかったです。

一から作るのも、
GCM: Getting Started
ここの手順をなぞればさくっと出来る感じです。
僕はさくっと出来なかったので備忘録かわりに書いておきます。

Sender IDを取得

Google APIs Console page
Google APIs ConsoleでGCM serviceをONにします。

API projectがまだない場合はCreate project...します。
プロジェクトを作るとアドレスバーに

https://code.google.com/apis/console/#project:4815162342

こんな感じでproject IDが表示されるので#project:の後の数字を控えておきます。
このIDがC2DMのSender IDに該当します。
(上のIDはご本家に書いてあったのコピーしてるので読み替えて下さい)

GCM serviceをONにする為に、

  1. 左側のメニューからServicesを選択
  2. Google Cloud MessagingのトグルをON
  3. 何か言われるのでとりあえずaccept

API Key(=Authorization key)を取得

C2DMの時に使っていたAuthorization keyはここで作ります。

  1. 引き続きAPIs ConsoleでAPI Accessを選択し、Create new Server key。
  2. IP制限とか必要なければそのままCreate。
  3. Key for server apps (with IP locking)のAPI key:の値をメモ。

これでSender IDとAuthorization keyの作成は終わり。
次はアプリ側を作ります。

アプリ側

便利なライブラリをご本家が公開してくれたので、SDK Managerを使って落とします。
Extras > Google Cloud Messaging for Android Library
です。

gcm.jarってのが、YOUR_SDK_ROOT/extras/google/gcm-client/distの中にあるので組み込みます。

お次はちょい面倒なManifestの編集。

<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="xx"/>

GCMはAndroid 2.2以上でしか使えません。

次に

<permission android:name="my_app_package.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="my_app_package.permission.C2D_MESSAGE" /> 
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

my_app_packageのとこはアプリのパッケージ名を入れる。
この辺はC2DMと同じ。

applicationの中は、こんな感じで。

<application android:icon="@drawable/icon" android:label="@string/app_name">
  <service android:name="my_app_package.GCMIntentService" />

  <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="my_app_package" />
    </intent-filter>

  </receiver>
</application>

GCMBroadcastReceiverはライブラリの中に入ってて、my_app_package.GCMIntentServiceは自分で用意します。

GCMIntentService

GCMBaseIntentServiceを継承してGCMIntentServiceクラスを作ります。
GCMBaseIntentServiceもライブラリの中にあります。

メッセージのレシーバは
Android開発 C2DMを触ってみよう
を参考にさせてもらいました。

コンストラクタでさっきメモったproject ID(=Sender ID)を親クラスに渡します。

package my_app_package;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Message;
import android.util.Log;

import com.google.android.gcm.GCMBaseIntentService;

public class GCMIntentService extends GCMBaseIntentService {
	
    public GCMIntentService() {
        super("SENDER_ID");
    }

    @Override
    public void onRegistered(Context context, String registrationId) {
        Log.w("registration id:", registrationId);
        sendMessage("id:" + registrationId);
    }
 
    @Override
    protected void onUnregistered(Context context, String registrationId) {
        sendMessage("C2DM Unregistered");
    }
 
    @Override
    public void onError(Context context, String errorId) {
        sendMessage("err:" + errorId);
    }
 
    @Override
    protected void onMessage(Context context, Intent intent) {
        String str = intent.getStringExtra("message");
        Log.w("message:", str);
        sendMessage(str);
    }
   
    private void sendMessage(String str) {
        //本体側に通知するなりなんなり
    }
}

registrationした時はonRegistered、なにかメッセージが来るとonMessageに飛んできます。
あとは、

onRecoverableError(Context context, String errorId)

なんてのもあるみたい。


レシーバが書けたら本体のActivityをいじります。

import com.google.android.gcm.GCMRegistrar;

して、onCreateの中で

GCMRegistrar.checkDevice(this);
GCMRegistrar.checkManifest(this);
final String regId = GCMRegistrar.getRegistrationId(this);
if (regId.equals("")) {
  GCMRegistrar.register(this, "SENDER_ID");
} else {
  Log.v(TAG, "Already registered");
}

ここでもSender IDを使います。
GCMRegistrar.checkDevice()でデバイスが対応しているかどうか、
GCMRegistrar.checkManifest()でマニフェストの設定があってるか見てくれます。
NGの時は例外投げるそうです。

これでアプリを起動すると、registration IDが取れるはずです。
registration IDも後で使うのでメモ。

サーバ側

アプリでregistration出来たら、サーバからpushしてあげればメッセージが受け取れます。

本家では便利なライブラリ使ってサーブレット作ってねって言ってましたが
サーブレットっておいしいの?な僕はまた
Android開発 C2DMを触ってみよう
を参考にさせてもらいました。
最初の方で取ったAPI keyとさっきのregistration IDを使います。

<?php

$url = 'https://android.googleapis.com/gcm/send';

$registration_id = 'AAAAAAAAAAAAAAAAAAA'; //registration IDはここ
$message = 'Hello, GCM!!';

$header = array(
  'Content-Type: application/x-www-form-urlencoded;charset=UTF-8',
  'Authorization: key=XXXXXXXXXX', //API keyはここ
);
$post_list = array(
  'registration_id' => $registration_id,
  'collapse_key' => 'update',
  'data.message' => $message,
);
$post = http_build_query($post_list, '&');

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FAILONERROR, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);

//CA証明書の検証をしない
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

$ret = curl_exec($ch);
 
var_dump($ret);

?>

このスクリプトを叩くとレシーバのonMessageがコールされます。
成功した時はPHP側で"id=XXXXXXX"とメッセージのIDが表示されます。

投げ先のURLや、GCMサーバの応答などなどは
GCM Architectural Overview
に細かく載ってます。

Interpreting a success responseやInterpreting an error response辺りに
レスポンス周りが載ってるので、上手くいかない時は参考になるかも。


★2014.3.18追記
コメントしていただいた、

//CA証明書の検証をしない
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,false);

をサーバ側のスクリプトに追記しました!

★2016.1.6追記
上記の追記したスクリプトの変数名が元のスクリプトと合っていなかったので修正しました!
コメントありがとうございました。

まとめ

はてな記法ちゃんと使えばもっと読みやすくなるんだろうか。。。