X

GCMを使用してAndroid-PHPでPUSH通知を実装する [Google Play services対応]

GCM(Google Cloud Messaging Service)はAndroidでPUSH通知を行うためのGoogleのサービスです。
※本記事はGoogle Cloud Messaging for Android の非推奨化に伴ってGoogle Play servicesに対応した版に@mhidakaが加筆、改定しました。

対応したプロジェクトファイルはGCMSampleからダウンロード可能です

GCMを使用するとLINEなどのメッセージングアプリが実装しているPUSH通知機能を簡単に実装できます。PUSH通知は情報をリアルタイムで送信できることから、昨今のアプリ開発では重要視されている機能です。

この記事ではサーバーではポピュラーな言語であるPHPを使ってサーバー実装を紹介します。

詳細は以下から。

サンプルコードは下記からダウンロードできます。
GCMSample1.zip

本記事では下記の順番でPUSH通知を実装方法を紹介していきます。

  1. GCMの構成
  2. APIキーの作成とprojectIDの取得
  3. Google Play servicesのインポート
  4. Androidアプリの実装
  5. pearのインストール
  6. 端末固有のレジストレーションIDを登録するサーバープログラム作成
  7. 送信用サーバープログラム作成

1. GCMの構成要素

GCMでのPUSH通知はGCMサーバーへ端末を登録し、IDを取得してから初めて行えるようになります。

GCMからIDを取得するとPHPなどのサーバープログラムからGCMに送信先の端末IDと送信したいメッセージを送信することで目的の端末にPUSH通知が送信されます。

2. APIキーの作成とprojectIDの取得

GCMではアプリを特定するためにAPIキーを取得する必要があります。
APIキーはAPI Consoleサイトで下記の方法で取得出来ます。

左メニューからAPI Accessを選択

create new server keyボタンを押下

アクセス可能IPアドレスの設定
特定のIPアドレスのみアクセスを許可したい場合はここでIPアドレスを設定します。
社内システムなど、限られたサーバーからのアクセスのみ許可したい場合に使用します。
特にアクセス制限を設けない場合はそのまま”create”ボタンを押下してください。

APIキーを控える
ここまでの手順を実行すると下記のような項目が追加されていますので、APIキーを控えます。

APIキーが取得できたらprojectIDを控えます。
projectIDはAPI ConsoleサイトのURLの”project:”以降の数字になります。

3.Google Play servicesのインポート

AndroidアプリでGCMを使用するためにはGoogle Play servicesライブラリをインポートする必要がります。
Play servicesはSDK Managerで”Google Play services”という項目にチェックを入れると取得できます。

インストールが完了すると“[SDKまでのパス]/extras/google/google_play_services/libproject/google-play-services_lib/libs/”配下に目的のgoogle-play-services.jarが格納されています。
これをAndroidアプリのプロジェクトにインポートして下さい。

4. Androidアプリの実装

ここまででGCMを使用したアプリを作成するまでの前準備が揃いました。
次からはAndroidアプリのプログラミングに入ります。GCMに必要な機能を実装していきましょう。

マニフェストへの記述
GCMを使用するためにはAndroidManifest.xmlにいくつかの項目が必要です。次のサンプルのようにパーミッション、ブロードキャストレシーバーを追加します。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.techbooster.gcmsample"
    ...省略...
    <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="org.techbooster.gcmsample.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <uses-permission
        android:name="org.techbooster.gcmsample.permission.C2D_MESSAGE" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="org.techbooster.gcmsample.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>

        <receiver
            android:name=".GCMBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="org.techbooster.gcmsample" />
            </intent-filter>
        </receiver>

        <service android:name=".GCMIntentService" />

    </application>

</manifest>

まず最初にパーミッションを追加します。インターネットのほか、WAKE_LOCK、GET_ACCOUNTSを追加しています。
Android 4.0.4以上の端末であればGET_ACCOUNT(Googleアカウントのアクセス許可)パーミッションを必要としません。ただし、4.0.4未満のバージョンではGCMを利用するのに必須のパーミッションとなります。対象のOSバージョンを広げる場合は記述しておいたほうがいいでしょう。

次に独自のパーミッションとしてandroid:name=”org.techbooster.gcmsample.permission.C2D_MESSAGE”を定義していますが、これはサンプルアプリのパッケージ名がorg.techbooster.gcmsampleとなっているためこのように記載しています。
実際にアプリに組み込む場合はandroid:name=”[アプリのパッケージ名].C2D_MESSAGE”と記載してください。

ブロードキャストレシーバも登録しておきます。GCMBroadcastReceiverの内容は後述しますが、com.google.android.c2dm.intent.RECEIVEアクションを受け取るのに利用します。カテゴリにアプリのパッケージ名を記載して、自分当てのメッセージのみブロードキャストされるようにフィルタリングします。実際にアプリを組み込む場合は、カテゴリも<category android:name=”[アプリのパッケージ名]” />に変更してください。

最後にブロードキャストレシーバーが受け取ったメッセージを処理するサービスを追加します。
GCMBaseIntentServiceクラスを継承したサブクラス(IntentService)を用意しています。GCMBaseIntentServiceに関しては後ほど改めて解説します。

定数クラスを作成する
productIDやサーバーのURLなど、変化することのない変数を定数としてまとめて宣言します。
このようにサーバーやアプリ固有の情報をまとめることでURL変更などが発生しても変更箇所を一箇所に絞れます。アプリの保守性を高める手法のひとつです。

下記はサーバー通信に関する文字列などを定数として列挙しているクラスです。

CommonUtilities.java

public final class CommonUtilities {

    /**
     * サーバーURL
     */
    static final String SERVER_URL = "https://techbooster.org";

    /**
     * プロダクトID
     */
    static final String SENDER_ID = "xxxxxxxxxxxx";

}

サンプルでは、サーバーのURL、プロダクトIDをまとめることとしました。
ここには登場していませんが、もう一つ重要な要素として端末固有のレジストレーションIDがありますが端末ごと生成されるためサンプルではプリファレンスで別途保存しています。

ブロードキャストを受け取るGcmBroadcastReceiverを作成する
GcmBroadcastReceiverはアプリ宛のメッセージを受け取るためのブロードキャストレシーバーです。アプリ宛のGCMメッセージがあった場合に呼び出されます。サンプルではWakefulBroadcastReceiverクラスのサブクラスとして実装しています

GcmBroadcastReceiver.java

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // intentをGcmIntentServiceで処理する
        ComponentName comp = new ComponentName(context.getPackageName(),
                GcmIntentService.class.getName());
        // サービスを起動、サービス動作中はWakeLockを保持する
        startWakefulService(context, (intent.setComponent(comp)));
        setResultCode(Activity.RESULT_OK);
    }
}

WakefulBroadcastReceiverにはサービス起動中にWAKE_LOCKをかけてくれる便利なstartWakefulServiceメソッドがあり、今回はこれを活用しています。

intentの送信先サービスGCMIntentServiceはサンプルでは、受け取ったメッセージをノーティフィケーションに通知するように実装しています。ぜひダウンロードして確認してみてください。

Activityの作成
引き続いてアプリの画面として起動するActivityを作成しましょう。
サンプルでは”登録”と”削除”の2つのボタンが有り、登録を押下するとGCMからIDを取得し、開発者サーバーへIDが記載されたテキストデータを保存します。
削除を押下すると、GCMサーバーからIDを削除します。

まずはレイアウトからです。
ボタンが2個のシンプルなレイアウトです。

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btn_regist"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="登録" />

        <Button android:id="@+id/btn_unregist"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="削除"/>

    </LinearLayout>

</LinearLayout>

レイアウトが用意できたらActivityを実装していきます。

まずは各ボタンにonClickListenerをセットしましょう。

MainActivity.java

public class MainActivity extends Activity implements OnClickListener{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btnRegist = (Button) findViewById(R.id.btn_regist);
        btnRegist.setOnClickListener(this);
        Button btnUnregist = (Button) findViewById(R.id.btn_unregist);
        btnUnregist.setOnClickListener(this);

        context = getApplicationContext();

        // デバイスにPlayサービスAPKが入っているか検証する
        if (checkPlayServices()) {
            gcm = GoogleCloudMessaging.getInstance(context);
            regid = getRegistrationId(context);
        } else {
            Log.i(TAG, "Google Play Services APKが見つかりません");
        }
    }

起動時にGoogleCloudMessagingからインスタンスを取得、保持しておきます。端末を識別するためのレジストレーションIDもすでに持っている可能性があるため、起動時に取得します(登録が完了して2回目以降の起動が該当します)。

checkPlayServicesメソッドではGoogle Playサービスアプリの有無を確認します。

    private boolean checkPlayServices() {
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        if (resultCode != ConnectionResult.SUCCESS) {
            if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
                GooglePlayServicesUtil.getErrorDialog(resultCode, this,
                        PLAY_SERVICES_RESOLUTION_REQUEST).show();
            } else {
                Log.i(TAG, "Playサービスがサポートされていない端末です");
                finish();
            }
            return false;
        }
        return true;
    }

GooglePlayServicesUtilはユーティリティクラスです。isGooglePlayServicesAvailableメソッドでGoogle Playサービスの利用可否を判断します。これはGoogle PlayサービスのAPK(アプリケーション、Playストア)がGCMを利用する前提になっているために必要な処理です。

引き続いて登録ボタンのOnClickListenerをみてみましょう。端末を識別するためのレジストレーションIDをGCMサーバーから取得します。このレジストレーションIDは独自サーバー(Push配信をしたいサーバー)でも利用するので、自分のサーバーへ送って登録するところを次に示します。

MainActivity.java

    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.btn_regist){
            if (regid.equals("")) {
                // GCM登録用AsyncTaskの実行
                mRegisterTask = new AsyncTask<Void, Void, Void>() {
                    @Override
                    protected Void doInBackground(Void... params) {
                        ...省略...
                        // GCMサーバーへ登録する
                        regid = gcm.register(CommonUtilities.SENDER_ID);
                        ...省略...
                        // レジストレーションIDを自分のサーバーへ送信する
                        // レジストレーションIDをつかえば、アプリケーションにGCMメッセージを送信できるようになります
                        Log.i(TAG,"送信対象のレジストレーションID: " + regid);
                        register(regid);

                        // レジストレーションIDを端末に保存
                        storeRegistrationId(context, regid);
                        return null;
                    }

                    @Override
                    protected void onPostExecute(Void result) {
                        mRegisterTask = null;
                    }
                };
                mRegisterTask.execute(null, null, null);
            }
        }
        ...省略...
    }

レジストレーションIDをすでに持っている場合は登録処理を行いません。2重登録を防ぐためです。登録自体はGoogleCloudMessaging#registerメソッドを呼び出すだけですので非常に簡単です。サーバーとの通信が必須なのでUIスレッドに影響しないようにAsyncTaskで実行してください。

取得したレジストレーションID(regid)は、registerメソッドを使って独自サーバーへ送信します。レジストレーションIDはPUSH配信するのにサーバー側で(独自サーバーからGCMサーバーへ通信するのに使う)利用するため、サーバーにも教える必要があります。登録した内容はAndroid端末にも保存しておき、2度目以降の起動で利用します。

解除時も処理の流れは同様です。

MainActivity.java

        ...省略...
        }else if(v.getId() == R.id.btn_unregist){
            //GCMサーバーから登録を解除するAsyncTaskの実行
            mRegisterTask = new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... params) {
                    if (gcm == null) {
                        // インスタンスがなければ取得する
                        gcm = GoogleCloudMessaging.getInstance(context);
                    }
                    try {
                        // GCMサーバーの登録を解除する
                        gcm.unregister();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    // レジストレーションIDを自分のサーバーでも削除する
                    unregister(regid);

                    return null;
                }
        ...省略...

解除時はGoogleCloudMessaging#unregisterメソッドを利用します。今回のサンプルではエラー処理を省略せずに掲載しています。GoogleCloudMessaging#registerメソッド、GoogleCloudMessaging#unregisterメソッド両方ともIOExpectionがthrowされます。実際に利用する際は登録/解除できなかったことを検知したら(多くの場合ネットワークに原因があります。)再試行またはエラーダイアログで告知するとよいでしょう。

proguardを使う場合

proguardを利用する際は動作がおかしくなることがあるのでjava.util.ListResourceBundleを難読化の対象外に指定します。

proguard-project.txt

-keep class * extends java.util.ListResourceBundle {
    protected Object[][] getContents();
}

5.pearのインストール

本記事ではサーバー側のプログラムでpearというライブラリを使用します。
pearにはphpで様々な機能を簡単に実装することができます。
今回はGCMへHTTP接続を行うため、pearのHTTP_Requestを使用します。

まずはpear本体をインストールしましょう。
centOSでは下記のコマンドでインストールが可能です。

yum -y install php-pear

pearのインストールが完了したら、HTTP_Requestをインストールします。
インストールには下記のコマンドを実行します。

pear install http_request

以上でサーバー側の前準備は完了です。

6. 端末IDの登録用サーバープログラム作成

まずはサーバー側でGCMから取得したIDを登録するプログラムを作成しましょう。

下記は”regID”という名称でPOSTパラメータとしてGCMから取得したIDを受け取り、受け取ったIDをファイルに保存するサンプルです。

register.php

<?php

$fp = fopen("id.txt", "a");
fwrite($fp, $_POST['regId']);
fclose($fp);

print('save ok');
?>

実際のサービスではこの部分でIDをDBなどに格納して管理を行ったりします。

7. 送信用サーバープログラム作成

最後にメッセージを送信するサーバープログラムを作成します。

下記はGCMから取得したIDを指定してPUSH通知要求をGCMサーバーに送信するサンプルです。

sender.php

<?php
require_once "HTTP/Request.php"

$regid = "";  // register.phpで取得したID
$apikey = ""; // API Key

$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", "posted from gcm");

if (!PEAR::isError($rq->sendRequest())) {
    print "\n" . $rq->getResponseBody();
} else {
    print "\nError has occurred";
}

?>

4行目の””内にはregister.phpでファイルに出力したIDを記述します。

5行目の””内にはAPI Consoleで取得したAPIキーを記述します。

以上、おつかれさまでした。

kei_i_t: