なるようになるかも

力は多くの場合、その人の思いを超えない。

AndroidのPush通知の話。

AndroidのPush通知はGoogle Play Serviceに統合されました。

いままではSDKマネージャーからGoogle Cloud Messaging for Androidをダウンロードして、gcm.jarをプロジェクトにインポートすることができたのですが、ついにSDKマネージャーからGoogle Cloud Messaging for Androidが消滅しました。

deprecatedの予告は昨年の7月ごろ行われ、クローズまで半年程度の猶予があったんですけれども、それ以降に出た本でgcm.jarを使うことを前提してる本があります。

具体的には「Effective Android」や「iPhone/Android 「通知機能」プログラミング徹底ガイド」です。

断言しておくと、 Push通知の実装を目的にこれらの本を買うべきではありません

新しいPush通知APIの違い

今後はGoogleCloudMessagingクラスの利用が推奨されていますが、実はPush通知部分で行われる通信そのものは、変わりません。

チャットのような機能が付いただけです。

そのため、C2DMからGCMの移行時とは異なり、マイグレーションも必要ありません。

登録処理をバックグラウンドスレッドで行うこと

Implementing GCM Clientで説明されていますが、

Note that because the GCM methods register() and unregister() are blocking, this has to take place on a background thread.

従来のregister()unregister()は内部でサービスを起動して通信をしているので、メインスレッドから呼び出すことができました。

GoogleCloudMessagingクラスの同名メソッドは、呼び出し元スレッドをブロックするため、バックグラウンドスレッドからコールする必要があります。

Registration IDの取り扱い

アプリケーションのバージョンを更新したときに、Registration IDの再取得を行わないといけない (バージョンを跨いだ場合の動作は保証されない)という点に注意しなければなりません。

このことは、Registration IDの説明には書いてありません。

Registration ID

An ID issued by the GCM servers to the Android application that allows it to receive messages. Once the Android application has the registration ID, it sends it to the 3rd-party application server, which uses it to identify each device that has registered to receive messages for a given Android application. In other words, a registration ID is tied to a particular Android application running on a particular device.

Note: If you use backup and restore, you should explicitly avoid backing up registration IDs. When you back up a device, apps back up shared prefs indiscriminately. If you don't explicitly exclude the GCM registration ID, it could get reused on a new device, which would cause delivery errors.

「換言すれば、Registration IDは特定のデバイス上の特定のAndroidアプリケーションと紐付けられている」とまとめられていますので、「デバイス/アプリ毎に一意のIDなのだ」という風にしか解釈できません。ドキュメントの不備です。

ではなぜ今までこのことを意識する必要がなかったのでしょう?それはgcm.jarが存在したからです。

gcm.jarに含まれるGCMRegistrargetRegistrationId ()の実装を読めば分かるのですが、

public static String getRegistrationId(Context context) {
    final SharedPreferences prefs = getGCMPreferences(context);
    String registrationId = prefs.getString(PROPERTY_REG_ID, "");
    // check if app was updated; if so, it must clear registration id to
    // avoid a race condition if GCM sends a message
    int oldVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
    int newVersion = getAppVersion(context);
    if (oldVersion != Integer.MIN_VALUE && oldVersion != newVersion) {
        Log.v(TAG, "App version changed from " + oldVersion + " to " +
                newVersion + "; resetting registration id");
        clearRegistrationId(context);
        registrationId = "";
    }
    return registrationId;
}

内部的にRegistration IDとその値を保存した時のアプリのバージョンをPreferencesに永続化しており、取得時にアプリが更新されている場合には古い値を破棄していたのです。

しかし今後はRegistration IDの永続化を自前で行う必要があります。

  • アプリの更新された場合には、古いRegistration IDを破棄し、再取得を行う
  • 端末に対して紐付くIDなので、リストアの対象には含めない

という2点に気を使えば、あとは問題ないはずです。

前者については公式ドキュメントのImplementing GCM Clientに実装例があります。getRegistrationId ()でやっていたこととほぼ同じです。

しかし「リストアの対象に含めない」とは、具体的に何をどうすればいいのかさっぱりです。

実はAndroidには、Android Backup ServiceというPreferenceの内容をクラウド保存できるサービスがあります。iCloudを連想するところですが、それより1年前、Android 2.2の古代から存在します。もっとも、活用されている気配がないので、気にしなくてもよさそうです。

ICS以降ではadb backupコマンドで、端末内部のアプリデータをバックアップできます。新規プロジェクトを作成すると、android:allowBackup属性としてtrue(省略されている場合のデフォルト値もtrue)がセットされているのが分かります。これをfalseにすればadb backupを防げますが、個別のデータやファイルに対して指定することができないため、大きく利便性を損ないます。

かといって端末の固有識別のためにAndroid IDだのIMEIだのを取得したくないですし(そもそもAndroid IDは当てにならない)、どーすればいいのでしょうね。