Google Cloud Messagingでハマったこと

  • 78
    いいね
  • 3
    コメント
この記事は最終更新日から1年以上が経過しています。

皆さんが最新の情報や面白い情報を投稿する中で、
Tipsで恐縮ですが、19日目の投稿をします。

GoogleCloudMessagingを導入するにあたって、gcm.jarを利用する際にハマったことと、その対策を共有したいと思います。
始めに必要なProject IDやjarの入手方法はこちらを参考にしてください。
公式はこちら(英語)です。

僕がハマった点4つ

  1. Registration IDの再取得
  2. GCMBroadcastReceiverクラスは固定値を返す
  3. 単体テストの方法
  4. GCMRegistrar.setRegisteredOnServerには期限がある

1. Registration IDの再取得

  • Application update
  • Backup and restore

上記の条件のときは、発行されるRegistration IDが変わる可能性があります。
そのため、バージョンを確認して、前回起動時と異なっていた場合、
GCMRegiatrar.register(Context context, String senderId)
を叩くというロジックを入れなければなりません。

  • バージョンアップしたら、gcm.jar内のプリファで保存しているRegistration IDは自動で消去されます。
MainActivity.java
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int currentVersionCode = getCurrentVersionCode();
        /** バージョンが挙がった場合はregIdが異なる可能性がある。 */
        if (isNewVersion(currentVersionCode)) {
            /** regIdに変更があるかもしれないのでサーバの登録状態を解除する
             * regIdも空になる */
            GCMRegistrar.setRegisteredOnServer(this, false);
        }
        setVersionCode(currentVersionCode);
        final String regId = GCMRegistrar.getRegistrationId(this);

        /** regIdが空の場合、GCMサーバに登録できていないので、
         * GCMRegistrar.registerを叩く。 */
        ((TextView)findViewById(R.id.gcm_text)).setText(regId);
        if (TextUtils.isEmpty(regId)) {
            /** 結果をGCMIntentService.javaで受け取る */
            GCMRegistrar.register(this, Const.SENDER_ID);
        }else {
            /** GCM登録が問題ないので、自社サーバ の登録期間を延長する*/
            GCMRegistrar.setRegisteredOnServer(this, true);
            GCMRegistrar.setRegisterOnServerLifespan(this, GCMRegistrar.DEFAULT_ON_SERVER_LIFESPAN_MS);
        }
    }


    @Override
    protected void onDestroy() {
        GCMRegistrar.onDestroy(this);
        super.onDestroy();
    }


    /**
     * 現在のバージョンコードを取得*/
    private int getCurrentVersionCode(){
        PackageInfo packageInfo = null;
        try {
            packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_ACTIVITIES);
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        return packageInfo.versionCode;
    }


    /**
     * 現在のバージョンコードをプリファに保存*/
    private void setVersionCode(int currentVersionCode){
        SharedPreferences pref = getSharedPreferences("gcmexample", MODE_PRIVATE);
        pref.edit().putInt("versionCode", currentVersionCode).commit();
    }


    /**
     * 現在のバージョンコードとプリファに保存された値を比較*/
    private boolean isNewVersion(int currentVersionCode){
        SharedPreferences pref = getSharedPreferences("gcmexample", MODE_PRIVATE);
        int oldVersionCode = pref.getInt("versionCode", 0);
        if (currentVersionCode > oldVersionCode) {
            return true;
        }
        return false;
    }


}

GCMRegistrar.getRegistrationId(Context context)の値が空のときは、
GCMRegiatrar.register(Context context, String senderId)
を呼び、Registration IDを発行しなければなりません。

2. GCMBroadcastReceiverクラスは固定値を返す

GCMRegiatrar.register(Context context, String senderId)が呼ばれると、
com.google.android.c2dm.intent.REGISTRATIONのactionが投げられるので、
GCMBroadcastReceiverを継承したクラスで受け取ります。
GCMBroadcastReceiverを継承したクラスでは、GCMBaseIntentServiceを起動させます。
その際、packageName直下の階層に.GCMIntentService
をくっ付けたクラスを起動しようとするので、異なる階層にGCMBaseIntentServiceを継承したクラスを作成していた場合、
Serviceが起動しません。

GCMBroadcastReceiver.java
   protected String getGCMIntentServiceClassName(Context context) {
        return getDefaultIntentServiceClassName(context);
    }
   static final String getDefaultIntentServiceClassName(Context context) {
        String className = context.getPackageName() +
                DEFAULT_INTENT_SERVICE_CLASS_NAME;// = ".GCMIntentService";
        return className;
    }

そこで、GCMBroadcastReceiverクラスを次のように拡張します。

GCMReceiver.java
public class GCMReceiver extends GCMBroadcastReceiver{

    @Override
    protected String getGCMIntentServiceClassName(Context context) {
        /**
         * GCMBaseIntentServiceを継承しているクラス名を渡す*/
        return GCMIntentService.class.getName();
    }

}

こうすることで、Serviceを無事起動できます。

3. 単体テストの方法

Serviceのどのmethodが呼ばれるかは、GCMBaseIntentServiceがハンドリングしてくれます。
Pushを受け取ったときは、GCMBaseIntentServiceonMessgeが呼ばれます。
テストのために、adb で broadcastを投げても、そのままでは
GCMBroadcastReceiverが拾ってくれません。
テストの時には、次の一文をコメントアウトする必要があります。

android:permission="com.google.android.c2dm.permission.SEND"

AndroidManifest.xml
        <receiver
            android:name="com.sakebook.android.gcmexample.GCMReceiver" 
            <!--android:permission="com.google.android.c2dm.permission.SEND"  -->>
            <intent-filter>
                <!-- Receives the actual messages. -->
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <!-- Receives the registration id. -->
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="com.sakebook.android.gcmexample" />
            </intent-filter>
        </receiver>

こうすることで、
com.google.android.gsf以外からのbroadcastも受け取るようになります。
今回の例だと、次のようなintentを投げます。
adb shell am broadcast -a com.google.android.c2dm.intent.RECEIVE -n
com.sakebook.android.gcmexample/com.sakebook.android.gcmexample.GCMReceiver
--es "hoge" "huga"

4. GCMRegistrar.setRegisteredOnServerには期限がある

GCMRegistrar.setRegisteredOnServer(Context context, boolean flag)
は、自社サーバの登録の状態を設定できるmethodです。
このmethodをフラグとして、使う場合には注意が必要です。
一度trueに設定した場合でも、設定してからDEFAULT_ON_SERVER_LIFESPAN_MS(=7日間)がたった場合、このmethodはfalseを返すようになります。
永続的に自社サーバの登録状態の管理をしたい場合は、
GCMRegistrar.setRegisterOnServerLifespan(Context context, long lifespan)
で、lifespanを延長させてあげなければなりません。
もしくは、期間限定の利用を設定する際にも使えます。
GCMサーバの状態とは直接の関係はないので、ユーティリティとしての使い方をするのが正しい使い方だと思います。

--
以上です。
流れに沿って説明したので、GCMの使う際の参考になれば幸いです。

サンプルのソースコードはGitHubにあるので、ご自由にどうぞ。と言っても、クライアント側だけなので実際に使うにはサーバ側の設定が必要ですのでお忘れなく。

この投稿は Android Advent Calendar 201319日目の記事です。