bobuhiro11's diary

GCM - Google クラウドメッセージング

22 Oct 2013

GCMの使用を強いられたので,メモです. GCMはサーバからandroidアプリに対して, push通知を送る仕組み・サービスです. 2012/6/26に,公開されたようです. それ以前は,C2DMを使って実装していました. 簡単なサンプルですが,メモとして残しておきます.

こちらに置いています.

http://developer.android.com/google/gcm/index.htmlhttp://www.kotemaru.org/2013/07/28/android-push-message.htmlから,一部引用しています.

仕組み

と呼ぶことにします.

  1. はじめにGCMサーバから,端末のID( レジスタID )を取得する.
  2. レジスタIDを自サーバへ送信する.
  3. 自サーバから,レジスタIDを設定し,メッセージをGCMサーバへ送る.
  4. GCMサーバから各端末にメッセージが送信され,push通知される.

準備

まず,https://code.google.com/apis/console/から GoogleAPIを取得します.

  1. Service一覧から,Google Cloud Messaging for Androidのstatusをonにします.
  2. API ACCESSから,Create new Server Keyを選択し,API Keyを取得します.

ここで,2つの情報を確認しておきます.

1. クライアントの実装

古いですが,Google APIs 10で実装しました. はじめに,Android SDK ManagerからGoogleCloudMessaging for Android Libraryをインストールします.次に,{Android SDKディレクトリ}/extras/google/gcm/gcm-client/dist/gcm-src.jarを,プロジェクトに追加します.(srcディレクトリで右クリック => Archive Fileで追加できます)

マニフェストファイル

マニフェストファイルに以下のようにpermissionを追加します. BEGIN,ENDで囲ったところを追加してください. com.example.comのところはプロジェクトのパッケージ名に変更します.

<?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 {

	/* 自サーバの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);
		
		/* GCMサーバからレジスタIDを取得する.
		 * 登録済みなら,それを自サーバへ送信する.
		 * 未登録なら,GCMResistrar.register()でGCMサーバへ登録する.
		 * (自サーバへは,Registeredのタイミングで送信される)
		 */
		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();
    }
	
	/**
	 * レジスタIDを自サーバへ送信する.
	 * @param regId レジスタ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);
    }

    /**
     * レジスタIDがGCMサーバへ登録されたときに呼ばれる.
     * ここで,自サーバへも送信します.
     */
    @Override
    protected void onRegistered(Context context, String regId) {
        Log.i(tag, "onRegistered: regId = " + regId);
        MainActivity.sendMyServer(regId);
    }

    /**
     * GCMサーバからメッセージを受信した時によばれる.
     */
    @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. サーバの実装

<?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. エラーコードとか

いくつかエラーを経験したので,その対処法.

java.lang.IllegalArgumentException: Receiver not registered: [email protected]

=> GCMへの登録を行う際に,GCMRegistrar.onDestroy(getApplicationContext());のように,contextを修正する.

ACCOUNT_MISSING

=> その端末にGoogleアカウントでログインされていないと発生します.特にエミュレータでは注意が必要です.


comments powered by Disqus < セキュリティ・キャンプ2013 Rubyの実装一覧のメモ >