2014/07/20

Android:AppEngineBackend with GCMメモ

Google Cloud Messaging for Android(GCM)を利用するためのメモ。

メモ:

  • 最大4kbのペイロード・データのメッセージを送出可能
  • Android2.2以上かつGooglePlayStoreがインストールされた端末が必要
  • Android3.0以前の端末ではGoogleアカウントがセットアップされている必要がある
  • GCMはGoogle Play serviceに組み込まれた(com.google.android.gms.gcm)
  • 従来のAPI群は非推奨に(com.google.android.gcm)
  • GoogleCloudMessaging(API)の利用にはGoogle Play services version 3.1以上が必要
  • メッセージ送信時に対象デバイスがオフラインであってもGCMはこれをキューイング/ストア>再送する機能を持つ
  • 3rdPartyサーバはexponential backoffの方式を組み込み、GCMサーバと通信すること
  • GCMメッセージはアプリ固有のPermission付きBroadcastIntent経由で通知される
  • GCMメッセージを受信すると対象アプリが起動されるため、メッセージ監視のためにアプリ起動し続ける必要はない
  • メッセージ処理はWakeLockを取得してService上で実行するが吉
  • GCMメッセージを受信したくない場合はunregisterも可能
  • Genymotionを使用する場合のBackendサーバアドレスは10.0.3.2

GCM基本フロー

  1. アプリケーションはGCMを有効化するための登録を行う
  2. 3rdPartyサーバはデバイス向けメッセージをGCMに送信
  3. アプリケーションはGCMからメッセージを受信する

準備するもの

  1. Google API Projectの作成Google Developers Console
  2. Project Numberを控える(GCM sender IDとして使用.)
  3. API \& auth.からGoogle Cloud Messaging for Androidを有効化.
  4. GCMのServer Keyを作成しAPI Key取得(3rdPartyサーバとGCMサーバ間認証で使用)参考:Obtaining an API Key

3rdPartyサーバの作成

GCMサーバ(GCM connection server)とやりとりするサーバを作成する必要があります。
Implementing GCM Server

App Engine Backend with GCM

試験用途であればApp Engine Backend with Google Cloud Messagingで手軽にGCMバックエンドを作成できます。
利用するには新規ModuleとしてBackendモジュールを作成します(AndroidStudio0.8.2以降)。

Backendモジュールの作成から実行までの詳細手順は下記を参考。
“App Engine Backend with Google Cloud Messaging” Template

Backendモジュール導入に必要な作業は次の通りです。

  1. 取得済みGCMのAPI Keyを登録
  2. build.gradleの編集
  3. Backendサーバへのデバイス登録処理の実装

GCM API Keyを登録

Backendモジュールを作成したらGCMのAPI Keyを登録します。
API Keyは<backend>/src/main/webapp/WEB-INF/appengine-web.xmlに定義されているので、
これを取得済みのものに置き換えます。

<property name="gcm.api.key" value="YOUR_KEY_HERE"/>

YOUR_KEY_HEREを取得済みのAPI Keyに置換。

<property name="gcm.api.key" value="AIza..."/>

これでAPI Keyの登録は完了です。

build.gradleの編集

(この作業はAndroidStudio v0.8.2より後のバージョンでは不要になるかもしれません)
AndroidStudio v0.8.2でBackendモジュールとメインとなるappモジュールを関連づけすると、
ビルドで問題(preDexLibraryタスクでOutOfMemoryやIllegalArgumentException)が発生します。
これを回避するためにappからappengine-endpoints(-deps)ライブラリのコンパイル指定をコメントアウトします。

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:19.0.+'
    compile 'com.google.android.gms:play-services:5.0.77'
//    appengine-endpoints(-deps)はBackendモジュールに含まれているため、appモジュールでの個別指定は不要
//    compile 'com.google.appengine:appengine-endpoints:1.9.1'
//    compile 'com.google.appengine:appengine-endpoints-deps:1.9.1'
    compile project(path: ':gcmbackend', configuration: 'android-endpoints')
}

Backendサーバへのデバイス登録処理の実装

下記のGcmRegistrationAsyncTaskクラスを取り込みます。
2.2. Registering devices with Google Cloud Messaging backend

GcmRegistrationAsyncTaskに定義されているSENDER_IDを自分のもの(GCM APIのProject Number)に置換します。
また、BackendサーバのURIを必要に応じて変更します。

  • Android Emulatorを使用している場合:http://10.0.2.2:8080/_ah/api/(変更なし)
  • Genymotionを使用している場合:http://10.0.3.2:8080/_ah/api/

Android Emulatorにおける10.0.2.2はホストループバックインターフェイスのエイリアスです(localhost)。
Genymotionでは10.0.3.2がこれにあたります。

全ての作業が終わったらBackendモジュールを実行し、ローカルサーバを起動します.
localhost:8080でGCMを送信するフォームが表示されます。
適当なActivityからGcmRegistrationAsyncTaskのexecuteを実行するとBackendサーバに端末のRegistrationIDが登録されます。
RegistrationID登録後にGCM送信フォームからテキストを送信すると端末にPush通知が届きます。

Androidアプリケーションの作成

GCMクライアントとなるAndroidアプリケーションを作成する場合はGoogleCloudMessaging APIの使用が推奨されています。

Google Play Serviceのセットアップ

アプリケーションの作成にはGoogle Play Serviceを使用します。
アプリケーションはGoogle Play ServiceのResourceにアクセスするため、単純に.jarを参照するだけでは動作しません。
AndroidStudioの場合はbuild.gradleのdependencyセクションに次を追記します。

dependencies {
    compile "com.google.android.gms:play-services:3.1.+"
}

GCMを使用するにはGoogle Play Service3.1以上である必要があります。

AndroidManifest.xmlの編集

次のPermissionを追加します。

  • com.google.android.c2dm.permission.RECEIVE: GCMサービスへの登録とメッセージ受信に必要
  • android.permission.INTERNET: 3rdPartyサーバへのRegistrationIDの登録に必要
  • android.permission.WAKE_LOCK: (optional)メッセージ受信時のWakeLock取得に必要
  • YourApplicationPackage + ".permission.C2D_MESSAGE": 他アプリからGCMへ登録されることやメッセージを横取りされるのを防ぐのに必要
  • com.google.android.c2dm.intent.RECEIVE: categoryに自アプリのパッケージ名を指定する。GCMメッセージ受信に必要
  • android.permission.GET_ACCOUNTS: GCMに必要なGoogleアカウント情報取得に必要(Android4.0.4以前で必要)
    a

    <!-- AndroidManifest.xml -->
    <manifest package="com.example">
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.WAKE_LOCK" />
        <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
        <!-- Android4.0.4以前をサポートする場合に必要 -->
        <uses-permission android:name="android.permission.GET_ACCOUNTS" />
        <!-- YourApplicationPackage + ".permission.C2D_MESSAGE" -->
        <permission android:name="com.example.permission.C2D_MESSAGE"
                android:protectionLevel="signature" />
        <uses-permission android:name="com.example.permission.C2D_MESSAGE" />
    

GCMの有効化

アプリケーションが初めてGCMを利用する前にGoogleCloudMessaging(API)のregister()メソッドを使ってGCMサービスへ登録する必要があります。
このメソッドはRegistrationIDを返します。参考:GoogleCloudMessaging

GCMへサービス登録する前にGoogle Play Serviceの状態をチェックします。
Google Play Serviceが古いバージョンである場合、Google Play Serviceの更新をユーザに促します。
アプリ起動中にGoogle Play Serviceがダウングレードされるケース等を考慮して、バージョンチェックはonResume()で行います。

@Override
protected void onResume() {
    super.onResume();
    try {
        PlayServiceUtil.updatePlayServiceIfNeeded(this, new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                MyActivity.this.finish();
            }
        });
    } catch (PlayServiceUtil.PlayServiceException e) {
        // Play Serviceが使用できない致命的状態
        Log.e(TAG, "Play Service is not available.");
        this.finish();
    }
}

public static void updatePlayServiceIfNeeded(Activity activity, DialogInterface.OnCancelListener cancelListener) throws PlayServiceException {
    final int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(activity);
    switch (status) {
        case ConnectionResult.SUCCESS:
            return;
        case ConnectionResult.SERVICE_DISABLED:
        case ConnectionResult.SERVICE_INVALID:
        case ConnectionResult.SERVICE_MISSING:
        case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
            Dialog dialog = GooglePlayServicesUtil.getErrorDialog(status, activity, 0);
            if (cancelListener != null) {
                dialog.setOnCancelListener(cancelListener);
            }
            dialog.show();
            return;
        default:
            throw new PlayServiceException("Play Service is not available. status=" + status);
    }
}

メッセージ送信

3rdPartyサーバ(以降”サーバ”)がメッセージ送信する時のシーケンス

  1. サーバはGCMにメッセージを送信
  2. GCMサーバは送信対象のデバイスがオフラインであればメッセージをキューイング/ストアする
  3. GCMサーバは送信対象のデバイスがオンラインになればメッセージを送信する
  4. デバイスはメッセージを受信すると配送すべきアプリケーションに向けてメッセージをブロードキャストする。
    配送されるのはアプリ固有のPermissionが設定されたBroadcastIntent。
  5. メッセージ処理が些末なものでない場合はPowerManager.WakeLockを取得してService上で実行する

メッセージ受信

デバイスがGCMメッセージを受信した時のシーケンス

  1. システムは、受信したメッセージからKey/Valueペアを展開
  2. システムは、com.google.android.c2dm.intent.RECEIVEのIntentとextraにKey/Valueデータを載せてアプリケーションに連携
  3. アプリケーションはIntentからKey/Valueを展開してデータを受け取る

参考サイト:
1. Google Cloud Messaging -android developers-

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.