Android Tips #14 Google Cloud Messaging (GCM) でプッシュ配信する

catch
91件のシェア(ちょっぴり話題の記事)

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Google Cloud Messaging (以下GCM) は、Googleが提供するサービスです。
アプリに対してプッシュ配信を実装することができます。

GCMの実装は、クライアント側の実装とサーバー側の実装が必要ですので、それぞれ解説したいと思います。

1.事前準備

はじめに、 Google APIs Console から GCM の API の利用登録をします。
以下のURLにアクセスします。Googleアカウントへのサインインが求められる場合はサインインしましょう。
https://code.google.com/apis/console/

上図の画面が表示されるので「Create Project」をクリックします。

GoogleAPI のサービス一覧が表示されます。
この中から「Google Cloud Messaging for Android」を探し、有効にします。

サービスの認証画面が表示されるので、利用規約に同意し、Acceptします。
すると下図のように、先ほどの「Google Cloud Messaging for Android」が ON の状態になると思います。

次に、左側に表示されているメニューから「API Access」を選択します。

デフォルトで「Simple API Access」が登録されています。
GCMを実装するときにはここに記載されている「API key」が必要になります。
また、URLに含まれているプロジェクトのIDも必要になるのでメモを取っておきます。
IDは「project:」以下の数値です。

以上で事前準備は完了です。

2.実装(アプリ側)

ライブラリのインポート

アプリ側では、以下の実装が必要になります。

  • 端末のサービスへの登録(registrationIdの生成)
  • GCMを受信したときの動作の実装(Serviceの実装)

これらの実装には、Googleが提供しているGCM用のライブラリを使用します。
まず、SDKマネージャーを開いて「Google Cloud Messageing for Android Library」がインストールされているか確認します。
未インストールの場合はインストールしてください。

インストールが完了したら、ライブラリのファイルをコピーします。
通常であれば以下のパスにあります。

YOUR_SDK_ROOT/extras/google/gcm-client/dist/gcm.jar

gcm.jar ファイルをプロジェクト直下の libs フォルダにコピーし、ライブラリのインポートは完了です。

Manifest.xml の編集

次に、GCMが受信できるように Manifest.xml を編集します。
権限に以下の項目を追加します。

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

app_package はプロジェクトのパッケージ名を入れてください。
次に、GCM用の BroadcastReciever を定義します。
ライブラリに含まれている GCMBroadcastReceiver クラスをそのまま利用します。

<application android:icon="@drawable/icon" android:label="@string/app_name">
	<activity android:name="app_package.GCMSampleActivity" 
	    android:launchMode="singleTask">
	    <intent-filter>
	      <action android:name="android.intent.action.MAIN" />
	      <category android:name="android.intent.category.LAUNCHER" />
	    </intent-filter>
	</activity>
	<service android:name="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="app_package" />
	    </intent-filter>
	</receiver>
</application>

category の android:name には、先ほどと同様にプロジェクトのパッケージ名を入れます。
GCMIntentService クラスは GCM を受信したときの挙動を実装する、自分で作るクラスになります。
GCMSampleActivity クラスは適当に作成した起動時に表示される Activity です。あとで使います。

GCMIntentService クラスの実装

次に、GCMを受信する Service クラスを実装します。
このクラスはライブラリにある GCMBaseIntentService を継承させます。

package app_package;

import android.content.Context;
import android.content.Intent;
import android.util.Log;

import com.google.android.gcm.GCMBaseIntentService;

public class GCMIntentService extends GCMBaseIntentService {

	private static final String TAG = "GCMIntentService";
	
	public GCMIntentService() {
		super("SENDER_ID");
	}

	@Override
	protected void onRegistered(Context context, String registrationId) {
		// 登録完了
		Log.i(TAG, "onRegisted registrationId:" + registrationId);
	}

	@Override
	protected void onUnregistered(Context context, String registrationId) {
		// 登録解除完了
		Log.i(TAG, "onUnregistered registrationId:" + registrationId);
	}
	
	@Override
	protected void onMessage(Context context, Intent data) {
		// メッセージ受信
	}
	
	@Override
	protected void onError(Context context, String errorId) {
		// エラー
		Log.e(TAG, "onError errorId:" + errorId);
	}

}

親クラスのコンストラクタに渡している "SENDER_ID" には 先ほど控えていおいたプロジェクトIDを渡します。
メッセージを受信したときの処理は onMessage() 内に記述します。
例えば以下のように Notification (ステータスバー) に表示させるなど、自由な実装ができます。

@Override
protected void onMessage(Context context, Intent data) {
	// メッセージ受信
	String str = data.getStringExtra("message");
	NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
	Notification notification = new Notification(R.drawable.ic_launcher, str, System.currentTimeMillis());
	Intent intent = new Intent();
	PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, 0);
	notification.setLatestEventInfo(getApplicationContext(), str, "GcmSample", contentIntent);
	notificationManager.notify(R.string.app_name, notification);
}

Activity の実装

最後に、GCMサービスへの登録を行う実装を記述します。
今回は Activity の onCreate() 内に登録する処理を実装しました。

package app_package;

import com.google.android.gcm.GCMRegistrar;

import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;

public class GCMSampleActivity extends Activity {

	private static final String TAG = "GCMSampleActivity";
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// デバイス・マニフェストの確認
		GCMRegistrar.checkDevice(getApplicationContext());
		GCMRegistrar.checkManifest(getApplicationContext());
		// 登録済かどうかを判別
		String regId = GCMRegistrar.getRegistrationId(getApplicationContext());
		if (TextUtils.isEmpty(regId)) {
			// 未登録
			GCMRegistrar.register(getApplicationContext(), "SENDER_ID");
		} else {
			// 登録済
			Log.i(TAG, "登録済み");
		}
	}
	
}

以上でアプリ側の実装は完了です。
このアプリをデバッグし、無事にサービスに端末が登録ができると Logcat に registrationIdが表示されます。
この registrationId はあとで必要になるので控えておいてください。

3.実装(サーバー側)

サーバー側に関してはGooleのAPIに対してPOSTができれば何でもよいので、
自由に選定することができます(※ローカル環境でもOKです)。
今回は Node.js + Express で実装してみました。
また、今回は Node.js の記事ではないので、細かいところは割愛させていただきます。

構成

nore_module 内が今回使用したモジュール名になります。
実装時にはこれらのモジュールのインストールが必要です。

Google API への POST

GoogleのAPIへのPOSTするときに必要なパラメータは以下のとおりです。

registration_ids 端末ごとの登録IDのリスト(JSON形式) ※一度に1〜1000のidを登録可能
data 送りたい文字列(JSON形式で複数可能) ※ 4KBまで
collapse_key 送信するメッセージのグループ(任意の文字列)
time_to_live デバイスがオフラインの場合にメッセージを保持しておく期間(秒で指定)

registration_ids に渡された端末にプッシュ配信されるので、端末を限定してプッシュすることも可能です。

また collapse_key についてですが、これはメッセージをグループ化するために必要なパラメータです。
GCMでは、受信する端末がメッセージを受け取れないときにGCMストレージに保管しておき、受け取れる状態になったときに送信することができます。
そのときに複数のメッセージが溜まっていた場合、グループ化してあるメッセージのうち最新のメッセージだけが送信されます。大量のメッセージが送信されてしまうことを避けるためのしくみのようです。

その他のパラメーターは以下のページに記載されています。
http://developer.android.com/guide/google/gcm/gcm.html#send-msg

ソース

app.js

var express = require('express')
  , ejs = require('ejs')
  , http = require('http')
  , path = require('path')
  , routes = require('./routes/main');

var app = express();

app.configure(function(){
  app.set('port', process.env.PORT || 8150);
  app.set('view engine', 'ejs');
  app.set('view options', { layout: false });
  app.set('views', __dirname + '/views');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});

app.engine('ejs', require('ejs').renderFile);

app.configure('development', function(){
  app.use(express.errorHandler());
});

app.get('/', routes.index);
app.post('/post', routes.post);

http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});

main.js

exports.index = function(req, res){
	res.render('form.ejs', { title: 'Form' });
};

exports.post = function(req, res){
	var request = require('request');
	var api_key = 'API_KEY'; // APIキーを入れる
	// メッセージの作成
	var msg = {
		registration_ids: ['REGISTRATION_ID'], // 端末で登録した registrationId を入れる
		collapse_key: "update",
		time_to_live: 180,
		// 送信するデータ
		data: {
			message: req.body.msg
		}
	};
	request.post({
    	uri: 'https://android.googleapis.com/gcm/send',
    	json: msg,
    	headers: {
      		Authorization: 'key=' + api_key
    }
  }, function(err, response, body) {
  	res.redirect('/');
  })
};

API_KEY は API利用登録時に控えておいたものに、
REGISTRATION_ID は アプリ登録時に Logcat に出力したものに書き換えてください。

form.ejs

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= title %></title>
  </head>

  <body>
    <div>
      <h1><%= title %></h1>
      <form method="POST" action="/post">
      <fieldset>
          <p><label>Message</label><input type="text" name="msg"></p>
      </fieldset>
      <input type="submit" value="Post">
      </form>
    </div>
  </body>
</html>

実行結果

まずサーバーアプリを起動します。

node app.js

http://localhost:8150 にアクセスするとフォーム画面が表示されます。
適当な文字列を打って「Post」ボタンを押します。

端末を見てみると…

送信されます!

まとめ

今回は GCM によるプッシュ配信のしくみの作りかたについて解説しました。
今回のサンプルではIDなどベタ書きしているところがありましたが、実際のアプリで使うためには registrationId を DB などで管理しておく必要があるので、もう少し複雑になります。
プッシュ配信を実装したいときの参考にしていただければ幸いです!

  • guard700

    記事、拝見いたしました。大変参考になりました。
    1つ質問させてください。

    JSON形式で、GCMサーバーに送るパラメータregistration_idsとdataに
    複数の値が設定できるそうですが、
    2つずつ(A,B)指定した場合、端末AにメッセージA、端末AにメッセージB、
    端末BにメッセージA、端末BにメッセージBと
    計4通のメッセージが届くのでしょうか?
    それとも、端末AにメッセージA、端末BにメッセージBと
    計2通のメッセージが届くのでしょうか?
    どちらとなるでしょうか?

    お忙しいことと思いますが、
    ご回答よろしくお願いいたします。

  • http://www.facebook.com/people/Yuki-Suwa/1269908313 Yuki Suwa

    >guard700さん
    ご質問いただきありがとうございます!
    2つずつ指定した場合、「2つの値が入っているdata」が「2つの端末」にそれぞれ送られる形になります。
    記事の例では message というキーの値を使用していますが、仮に messageA, messageBという名前にしたとするとその data が丸ごと、端末A・Bそれぞれに送られます。
    アプリ側からは getStringExtra(‘messageA’) などのようにして data に入ってる各値をそれぞれ取得できます。
    他にも不明点がありましたら気軽にご連絡ください!

    • guard700

      回答ありがとうございました。
      やはり、4通送られるということですね。
      マルチキャストという観点だから、そうなるですかね。

      • http://www.facebook.com/people/Yuki-Suwa/1269908313 Yuki Suwa

        回数で言いますと、計2通です。
        端末A、端末Bには同じデータが送られるのですが、データの中に複数の値が入れられる、ということです。
        ですので、例えばdataの中にタイトルの値とメッセージの値を入れておけば、そのdataがそれぞれの端末に1通ずつ送られるしくみです。誤解を招いてしまう書き方で申し訳ありません…。