/var/log/messages

Jun 3, 2015 - 6 minute read - Comments - android

Implementing GCM Client on Android の要点

Implementing GCM Client on Android 関連確認入れてみたので控えを以下に。

はじめに

  • システム要件としては Android 4.0.4 以降のバージョン
  • GCM フル実装のためにはクライアント側、サーバ側の実装が必要

Step 1: Google Play Service の設定

  • アプリケーションの記述にあたっては GoogleCloudMessaging API を使いなさい
  • この API を使うためには Google Play Services SDK をプロジェクトに設定しなければならない

gradle 使っているのであれば build.gradle に以下の記述を追加すれば良い

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

Step 2: Edit Your Application’s Manifest

以下に列挙する事項を AndroidManifest.xml に盛り込む

  • Android アプリケーションが登録やメッセージ受信できるための com.google.android.c2dm.permission.RECEIVE パーミッション
  • Android アプリケーションが registration ID をサードパーティのサーバへの送信を行うための android.permission.INTERNET パーミッション
  • アプリケーションがメッセージ受信時のスリープから復帰するための android.permission.WAKE_LOCK パーミッション
  • 他のアプリケーションによる Android アプリケーションメッセージの受信や登録を防止するための applicationPackage + “.permission.C2D_MESSAGE” パーミッション
  • applicationPakage がセットされたカテゴリと一緒に送信される com.google.android.c2dm.intent.RECEIVE のためのレシーバ。レシーバは GCM フレームワークのみがそこにメッセージを送信できるよう、com.google.android.c2dm.permission.SEND パーミッションが必要となる。アプリケーションが IntentService (必須じゃないけど通常これを使う) を使う場合、レシーバは WakefulBroadcastReceiver のインスタンスであるべきである。WakefulBroadcastReceiver はアプリのために partial wake lock の生成と管理をしてくれます。
  • プロセスがスリープ状態に戻らないようにしながらGCM メッセージをハンドルする仕事を WakefulBroadcastReceiver から渡される Service (通常 IntentService)。IntentService の使用はオプションではありますが、普通はこれ使う(意訳)
  • GCM の feature が Android アプリケーションの機能としてクリティカルであるならば、manifest において android:minSdkVersion="8” の設定を確実に行うこと。これで動作しない環境に導入されないことを確実にできます。

以下が GCM をサポートするサンプルの manifest の抜粋:

<manifest package="com.example.gcm" ...>

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/>
    <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.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />

    <application ...>
        <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="com.example.gcm" />
            </intent-filter>
        </receiver>
        <service android:name=".GcmIntentService" />
    </application>

</manifest>

Step 3: Write Your Application

この節では GoogleCloudMessaging API の使い方を説明するサンプルを feature します。サンプルは以下で構成されます。

  • メイン Activity (DemoActivity)
  • WakefulBroadcastReceiver (GcmBroadcastReceiver)
  • IntentService (GcmIntentService)

ソースコードはここから入手可能な模様。

以下に注意:

  • とりわけ、サンプルでは upstream (device-to-cloud) メッセージングと登録処理を説明しています。上流のメッセージングのみCCS(XMPP)サーバーに対して実行されているアプリに適用されます。 HTTP べースのサーバは、上流のメッセージングをサポートしていません。
  • GoogleCloudMessaging API は廃止されたクライアントのヘルパーライブラリを基にした古い登録プロセスを置き換えます。古い登録プロセスはまだ動作しますが、新しいものを使いましょう。

Check for Google Play Services APK

  • サンプルアプリにおいては二カ所でチェックされています:メイン Activity の onCreate() および onResume()
  • GooglePlayServicesUtil.getErrorDialo() を呼び出して Google Play Services APK コンパチなアプリの導入をする模様

以下が実装例です。

private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
...
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);
    mDisplay = (TextView) findViewById(R.id.display);

    context = getApplicationContext();

    // Check device for Play Services APK.
    if (checkPlayServices()) {
        // If this check succeeds, proceed with normal processing.
        // Otherwise, prompt user to get valid Play Services APK.
        ...
    }
}

// You need to do the Play Services APK check here too.
@Override
protected void onResume() {
    super.onResume();
    checkPlayServices();
}

/**
 * Check the device to make sure it has the Google Play Services APK. If
 * it doesn't, display a dialog that allows users to download the APK from
 * the Google Play Store or enable it in the device's system settings.
 */
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, "This device is not supported.");
            finish();
        }
        return false;
    }
    return true;
}

Register for GCM

  • Android アプリケーションはメッセージが受信できるようになる前に GCM サーバへの登録を行う必要があります
  • アプリが登録される時、将来の使用に備えて registration ID を受け取ります。

以下の例は初期手続きにおける登録の確認を行う onCreate メソッドあたりの記述になります。

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        mDisplay = (TextView) findViewById(R.id.display);

        context = getApplicationContext();

        // Check device for Play Services APK. If check succeeds, proceed with
        //  GCM registration.
        if (checkPlayServices()) {
            gcm = GoogleCloudMessaging.getInstance(this);
            regid = getRegistrationId(context);

            if (regid.isEmpty()) {
                registerInBackground();
            }
        } else {
            Log.i(TAG, "No valid Google Play Services APK found.");
        }
    }

取得した registration ID は Shared Preferences に格納してます(getRegistrationId())

private String getRegistrationId(Context context) {
    final SharedPreferences prefs = getGCMPreferences(context);
    String registrationId = prefs.getString(PROPERTY_REG_ID, "");
    if (registrationId.isEmpty()) {
        Log.i(TAG, "Registration not found.");
        return "";
    }

    int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
    int currentVersion = getAppVersion(context);
    if (registeredVersion != currentVersion) {
        Log.i(TAG, "App version changed.");
        return "";
    }
    return registrationId;
}

private SharedPreferences getGCMPreferences(Context context) {
    return getSharedPreferences(DemoActivity.class.getSimpleName(),
            Context.MODE_PRIVATE);
}

registration ID が存在しないか、アプリが更新されていたら getRegistrationId() は新しい registration ID を取得する必要があることを示す空文字列を戻します。getRegistrationId メソドはチェックのために以下のメソドを呼び出します。

private static int getAppVersion(Context context) {
    try {
        PackageInfo packageInfo = context.getPackageManager()
                .getPackageInfo(context.getPackageName(), 0);
        return packageInfo.versionCode;
    } catch (NameNotFoundException e) {
        // should never happen
        throw new RuntimeException("Could not get package name: " + e);
    }
}
  • valid な registration ID が無い場合、DemoActivity は以下の registerInBackGround メソドを登録のために呼び出します。
  • register とか unregister というメソドはバックグラウンドで動作させる必要があることに注意

サンプルは AsyncTask を使ってます

private void registerInBackground() {
    new AsyncTask() {
        @Override
        protected String doInBackground(Void... params) {
            String msg = "";
            try {
                if (gcm == null) {
                    gcm = GoogleCloudMessaging.getInstance(context);
                }
                regid = gcm.register(SENDER_ID);
                msg = "Device registered, registration ID=" + regid;

                sendRegistrationIdToBackend();

                storeRegistrationId(context, regid);
            } catch (IOException ex) {
                msg = "Error :" + ex.getMessage();
            }
            return msg;
        }

        @Override
        protected void onPostExecute(String msg) {
            mDisplay.append(msg + "\n");
        }
    }.execute(null, null, null);
    ...
}

registration ID を受信したら AP サーバへも送付しておきましょう。

/**
 * Sends the registration ID to your server over HTTP, so it can use GCM/HTTP
 * or CCS to send messages to your app. Not needed for this demo since the
 * device sends upstream messages to a server that echoes back the message
 * using the 'from' address in the message.
 */
private void sendRegistrationIdToBackend() {
    // Your implementation here.
}

登録後にはアプリは storeRegistrationId() というメソドを shared preferences に registration ID を登録するために呼びだします。方法は別なものでもかまいません。

private void storeRegistrationId(Context context, String regId) {
    final SharedPreferences prefs = getGCMPreferences(context);
    int appVersion = getAppVersion(context);
    Log.i(TAG, "Saving regId on app version " + appVersion);
    SharedPreferences.Editor editor = prefs.edit();
    editor.putString(PROPERTY_REG_ID, regId);
    editor.putInt(PROPERTY_APP_VERSION, appVersion);
    editor.commit();
}
  • 基本的にサーバおよび SharedPreferences に登録を行う模様
  • GCM への register/unregister はこちらの端末識別情報も送付している模様

Handle registration errors

  • 上述したように、Androidアプリは、GCMサーバに登録する必要があり、それはメッセージを受信する前に、登録IDを取得します。

  • 指定された登録IDを無期限に続くことが保証されていないので、必ず最初にすべきことはあなたのアプリが(コードスニペットで上記に示したように)それが有効な登録IDを持っていることを確認するチェックです。

  • それが有効な登録IDを持っていることを確認することに加えて、あなたのアプリケーションは、登録エラーTOO_MANY_REGISTRATIONSを扱うように準備されるべきです。

  • このエラーは、デバイスがGCMに登録し、あまりにも多くのアプリケーションを持っていることを示しています。

  • エラーは、アプリケーションの極端な数がある場合に発生しますので、平均的なユーザーには影響を与えないようにしてください。

  • 救済策は、新しいもののための部屋を作るために、デバイスから他のクライアントアプリの一部を削除するように促すことです。

Receive a downstream message

以降は略

Android の登録 ID について

  • Android の登録 ID について より

  • サーバサイドでの登録解除についてはサーバ側で端末固有な id を保持している場合、重複登録があればそれを削除するという手法もある模様です

Send messages from the cloud

送信する JSON の例が以下

{
  "registration_id": "xxxxxxxxxxxxxx",
  "data": {
    "message": "This is a GCM Topic Message!",
   }
}

おわりに

以下を押さえた上で実装検討を行う必要があるようです。

  • 端末側では registration ID が更新されてしまう可能性がある
  • AP サーバ側にて更新されても良い仕組みを検討しておく必要がある
  • ただし、Preferences などに保存をする場合、必ずしも更新前の id を入手できるとは限らないため、サーバサイドで古い id を削除する仕組みを検討して実装しておく必要があると思われます(端末識別情報の保持なd)