背景
Android アプリ向けに提供している現存の SDK (Android ライブラリ)がプッシュ機能を提供しており、開発当時最新の API であった Google Cloud Messaging (GCM) により実装されている。
現在では GCM は Firebaes Cloud Messaging (FCM) として提供されるようになっており、機能の更新などは FCM に対してのみ行われるので FCM へのアップグレードが推薦されている。
アプリの GCM から FCM への移行については説明記事がたくさん存在するのだが、ライブラリという形態で提供される場合は事情が少し異なるので、補足が必要である。
この記事では GCM によるライブラリ提供形態でのプッシュ実装を、FCM に更新する方法を実地で説明する。
なお、実装コードは技術的問題点の輪郭をはっきりさせるために、ギリギリのスケルトン状態に削ってある。実際に利用する場合は種々の処理が追加で必要になってくるので注意してほしい。
(2018/4/16 追記) GCM が廃止され FCM に完全移行する時期がとうとう発表された(2019 年 4 月)。この記事は扱う状況が特殊なため限定公開としていたが、これを期に何かの役に立つ場合もあるかと公開記事にすることにした。しかし、「google-services.json 無しで FCM を利用したい場合」以外のケースではほとんど役に立たない記事であることはここで強調しておく。
結論
- GCM と FCM はよく似ているので、アプリ(ライブラリ)のコードの変更は特に難しくない。
- プッシュの実行(サーバーに対する API 呼び出し)も字面が違うだけでほぼ同じ。
- アプリによる Firebase の組み込み(google-services.json を Gradle プラグインで処理する方法)がライブラリでは利用できないので、代替手法を用いる必要がある。
- ライブラリを利用するアプリ側で Firebase を既に利用している、という事態が十分考えられるので、衝突を避ける方法が必要。
概要
- ライブラリに GCM によるプッシュを実装し、それを利用するサンプルアプリを用意して試せるようにする。
- ライブラリを FCM 実装に更新する。利用するサンプルアプリ側で Firebase を利用しない場合をまず実験する。
- サンプルアプリ側で Firebase を利用しても動作する方法を説明する。
このハンズオンでは Android Studio と curl と Web ブラウザを用いる。また、Android プロジェクトと Firebase プロジェクトが必要なので Google Cloud Platform のアカウントを用意する必要がある。利用料金が発生する場合があるので注意すること。
GCM 版
Google Cloud Platform (GCP) プロジェクトの作成
- https://console.cloud.google.com/cloud-resource-manager を開く。
- [+プロジェクトの作成] をクリックする。
- [プロジェクト名] に "my-push-migration" を入力する。
- [作成] をクリックする。
解説: 世の中に流布する GCM によるプッシュ実装の説明記事を読むと、まずここで Messaging API を有効にする必要があるのだが、現在では Messaging API を有効にして API キーを用意してもそれを用いて Messaging API を呼び出すことはもはやできない。今の時点で Messaging API (GCM API) を呼びたい場合は Firebase プロジェクトを作る必要がある。
Firebase プロジェクトの作成
- https://console.firebase.google.com/ を開く。
- [+プロジェクトを追加] をクリックする。
- [プロジェクト名] から "my-push-migration" を選択する。
- [国 / 地域] から "日本" を選択する。
- [FIREBASEを追加] をクリックする。
- [プランを確認] をクリックする。
Firebase にアプリを追加
- https://console.firebase.google.com/ を開く。
- プロジェクト "my-push-migration" をクリックする。
- [Android アプリに Firebase を追加する] をクリックする。
- [Android パッケージ名] に "com.example.dummyapp" を入力する。
- [アプリの登録] をクリックする。
- [続行] をクリックする。
- [終了] をクリックする。
解説: ここで追加するアプリのパッケージ名は、とくに何かと一致していなければならないということはない。google-services.json による Firebase 組み込みではアプリのパッケージ名と一致している必要があるが、本記事で説明する方法ではそれは必要ない。実用上はライブラリのパッケージ名にするのがよいであろう(統計情報などがまとまる場所なので)。ここでは一致していなくても問題ないということを強調するために dummyapp
とした。
サーバーキーと送信者 ID とアプリケーション ID の取得
- https://console.firebase.google.com/ を開く
- プロジェクト "my-push-migration" をクリックする。
- "com.example.dummyapp" の右端の方にある [...] をクリックし [設定] を選択する。
- [クラウドメッセージング] タブをクリックする。
- [サーバーキー] と [送信者 ID] をメモする。
- [全般] タブをクリックする。
- "com.example.dummyapp" のセクションの [アプリケーション ID] をメモする。
解説: Firebase でアプリの設定にたどり着く方法(右端の方の [...] をクリックする方法)が非常にわかりにくい。なんとかしてほしい。
解説: アプリケーション ID は GCM 実装では利用しない。後ほど FCM 実装で利用する。
GCM プッシュライブラリ&サンプルアプリ用 Android Studio プロジェクトの作成
- Android Studio を開く。
- [Start a new Android Studio project] をクリックする。
- [Application name] に "Push Migration" を入力する。
- [Company domain] に "example.com" を入力する。
- [Project location] にプロジェクトの保存先を指定する。
- [Next] をクリックする。
- [Phone and Tablet] のみにチェックを入れ、"API 16: Android 4.1 (Jelly Bean)" を選択する。
- [Next] をクリックする。
- "Empty Activity" を選択する。
- [Next] をクリックする。
- [Finish] をクリックする。
ライブラリモジュールの作成
- メニューから [File | New | New Module...] を選択する。
- "Android Library" を選択する。
- [Next] をクリックする。
- [Application/Library name] に "Push SDK" を入力する。
- [Finish] をクリックする。
ライブラリモジュールの実装
依存ライブラリの設定
- Project ペインの Gradle Scripts/build.gradle (Module: pushsdk) をダブルクリックする。
- dependencies に "
compile 'com.google.android.gms:play-services:11.8.0'
" を入力する。
// ...
dependencies {
// ...
compile 'com.google.android.gms:play-services-gcm:11.8.0'
}
- 上部に現れる [Sync Now] をクリックする。
PushRegistrationService
- Project ペインの pushsdk/java/com.example.pushsdk を右クリックし [New | Java Class] を選択する。
- [Name] に "PushRegistrationService" を入力する。
- [Superclass] に "android.app.IntentService" を入力する。
- [OK] をクリックする。
package com.example.pushsdk;
import android.app.IntentService;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID;
public class PushRegistrationService extends IntentService {
public PushRegistrationService() {
super("PushRegistrationService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
try {
String id = InstanceID.getInstance(this).getToken("送信者 ID",
GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
Log.i("pushsdk", id);
} catch (Exception e) {
Log.i("pushsdk", e.toString());
}
}
}
- ”送信者 ID" のところには「サーバーキーと送信者 ID の取得」でメモした実際の送信者 ID を入れることに注意。
PushReceiverService
- Project ペインの pushsdk/java/com.example.pushsdk を右クリックし [New | Java Class] を選択する。
- [Name] に "PushReceiverService" を入力する。
- [Superclass] に "com.google.android.gms.gcm.GcmListenerService" を入力する。
- [OK] をクリックする。
package com.example.pushsdk;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gms.gcm.GcmListenerService;
public class PushReceiverService extends GcmListenerService {
@Override
public void onMessageReceived(String s, Bundle bundle) {
String message = bundle.getString("message");
Log.i("pushsdk", "from:" + s + ", message: " + message);
}
}
PushSdk
- Project ペインの pushsdk/java/com.example.pushsdk を右クリックし [New | Java Class] を選択する。
- [Name] に "PushSdk" を入力する。
- [OK] をクリックする。
package com.example.pushsdk;
import android.content.Context;
import android.content.Intent;
public class PushSdk {
public static void initialize(Context context) {
context.startService(new Intent(context, PushRegistrationService.class));
}
}
AndroidManifest.xml
- Project ペインの pushsdk/manifests/AndroidManifest.xml をダブルクリックする。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.pushsdk" >
<application>
<service android:name=".PushRegistrationService" />
<service android:name=".PushReceiverService">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
</intent-filter>
</service>
<receiver android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE"/>
<category android:name="com.example.pushsdk"/>
</intent-filter>
</receiver>
</application>
</manifest>
サンプルアプリの実装
依存ライブラリの設定
- Project ペインの Gradle Scripts/build.gradle (Module: app) をダブルクリックする。
- dependencies に "
compile project(":pushsdk")
" を入力する。
// ...
dependencies {
// ...
compile project(":pushsdk")
}
- 上部に現れる [Sync Now] をクリックする。
解説: ここの compile
により pushsdk
が依存しているライブラリのマニフェストなどがマージされるようなので、サンプルアプリ自体のマニフェストにパーミッションなどを書き加える必要は特にない。
MainActivity
- Project ペインの app/java/com.example.pushmigration/MainActivity をダブルクリックする。
-
onCreate()
の最後にcom.example.pushsdk.PushSdk.Initialize(this);
を加える。
package com.example.pushmigration;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.example.pushsdk.PushSdk;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PushSdk.initialize(this);
}
}
サンプルアプリの実行
- メニューから [Run | Run 'app'] を選択する。
- Logcat ペインを開く。
-
/com.example.pushmigration I/pushsdk: XxXxXXxxX...
のように登録 ID が表示されているはず。- この登録 ID に宛ててプッシュメッセージを送信してみる。この際、「サーバーキーと送信者 ID の取得」でメモしたサーバーキーが必要となる。
$ export SVRKEY='サーバーキー' REGID='登録 ID'
$ cat <<'^Z' | bash
curl -H "Authorization: key=$SVRKEY" -H 'Content-Type: application/json' \
-d '{"registration_ids": [ "'$REGID'" ], "data": { "message": "Hello Android" }}' \
https://gcm-http.googleapis.com/gcm/send
^Z
- Logcat ペインに
/com.example.pushmigration I/pushsdk: from:送信者 ID, message: Hello Android
と表示されるはず(初回など数分遅れて表示されることがあるので待ってみる)。
FCM への移行
ライブラリモジュールの変更
依存ライブラリの変更
- Project ペインの Gradle Scripts/build.gradle (Module: pushsdk) をダブルクリックする。
- dependencies の "
'com.google.android.gms:play-services:11.8.0'
" を "'com.google.firebase:firebase-messaging:11.8.0'
" に変更する。
// ...
dependencies {
// ...
compile 'com.google.firebase:firebase-messaging:11.8.0'
}
- 上部に現れる [Sync Now] をクリックする。
PushRegistrationService
- Project ペインの pushsdk/java/com.example.pushsdk/PushRegistrationService をダブルクリックする。
package com.example.pushsdk;
import android.app.IntentService;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.firebase.FirebaseApp;
import com.google.firebase.iid.FirebaseInstanceId;
public class PushRegistrationService extends IntentService {
public PushRegistrationService() {
super("PushRegistrationService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
try {
String id = FirebaseInstanceId.getInstance().getToken();
Log.i("pushsdk", id);
} catch (Exception e) {
Log.i("pushsdk", e.toString());
}
}
}
解説: GCM 版では登録 ID を取得するために送信者 ID が必要だったが、FCM 版では必要ない。これは、PushSdk クラスで Firebase インスタンスを初期化するときに Firebase プロジェクトを指定して初期化したおかげ。
PushReceiverService
- Project ペインの pushsdk/java/com.example.pushsdk/PushReceiverService をダブルクリックする。
package com.example.pushsdk;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
public class PushReceiverService extends FirebaseMessagingService {
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
String s = remoteMessage.getFrom();
String message = remoteMessage.getData().get("message");
Log.i("pushsdk", "from:" + s + ", message: " + message);
}
}
解説: GCM 版と似ているが、メッセージ全体がオブジェクト firebase.messaging.remoteMessage
になり、データが android.os.Bundle
から Map<String, String>
に変わっている。
PushSdk
- Project ペインの pushsdk/java/com.example.pushsdk/PushSdk をダブルクリックする。
package com.example.pushsdk;
import android.content.Context;
import android.content.Intent;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
public class PushSdk {
public static void initialize(Context context) {
FirebaseOptions options = new FirebaseOptions.Builder()
.setApplicationId("アプリケーション ID")
.setApiKey("dummy")
.build();
FirebaseApp.initializeApp(context, options);
context.startService(new Intent(context, PushRegistrationService.class));
}
}
- ”アプリケーション ID” のところは「サーバーキーと送信者 ID の取得」でメモした実際のアプリケーション ID を入力することに注意。
解説: Firebase 利用のための初期化は、通常は Firebase コンソールからダウンロードしてきた google-services.json を gradle で処理してリソースに変換することで行われるが、ライブラリでは同様の初期化手法をとることはできない。そこで FirebaseApp.initializeApp()
を明示的に呼んで初期化する方法をとる。
解説: FCM の利用に限っては setApiKey()
では空文字以外のどんな文字列を指定しても動作するようだ。不安なら google-services.json をダウンロードしてその中に記述してある .client[0].api_key.current_key
の内容を指定したほうがいいかもしれない。ちなみにこの値は Web 上のコンソールでは見ることができない。
AndroidManifest.xml
- Project ペインの pushsdk/manifests/AndroidManifest.xml をダブルクリックする。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.pushsdk" >
<application>
<service android:name=".PushRegistrationService" />
<service android:name=".PushReceiverService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
</application>
</manifest>
解説: intent-filter.action
の名前が Firebase のものに変更された。<receiver />
は必要なくなった。Firebase ライブラリのマニフェストに記述されたものがマージされるおかげだと推察する。
サンプルアプリの実装
- サンプルアプリの実装に変更はない。
サンプルアプリの実行
- メニューから [Run | Run 'app'] を選択する。
- Logcat ペインを開く。
-
/com.example.pushmigration I/pushsdk: XxXxXXxxX...
のように登録 ID が表示されているはず。 - この登録 ID に宛ててプッシュメッセージを送信してみる。
$ export SVRKEY='サーバーキー' REGID='登録 ID'
$ cat <<'^Z' | bash
curl -H "Authorization: key=$SVRKEY" -H 'Content-Type: application/json' \
-d '{"registration_ids": [ "'$REGID'" ], "data": { "message": "Hello Android" }}' \
https://fcm.googleapis.com/fcm/send
^Z
- Logcat ペインに
/com.example.pushmigration I/pushsdk: from:送信者 ID, message: Hello Android
と表示されるはず(初回など数分遅れて表示されることがあるので待ってみる)。
解説: GCM とは URL が変わっているだけ。
既存 Firebase アプリとの共存
ライブラリモジュールの変更
PushRegistrationService
- Project ペインの pushsdk/java/com.example.pushsdk/PushRegistrationService をダブルクリックする。
package com.example.pushsdk;
import android.app.IntentService;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.firebase.FirebaseApp;
import com.google.firebase.iid.FirebaseInstanceId;
public class PushRegistrationService extends IntentService {
public PushRegistrationService() {
super("PushRegistrationService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
try {
String id = FirebaseInstanceId.getInstance(FirebaseApp.getInstance("pushsdk")).getToken();
Log.i("pushsdk", id);
} catch (Exception e) {
Log.i("pushsdk", e.toString());
}
}
}
解説: 既存 Firebase アプリでは、FirebaseInstanceId.getInstance()
で取得されるデフォルトの Firebase インスタンスはアプリで既に初期化されて使用されているのでライブラリ内で使用することはできない。そこで名前付きのデフォルトではない Firebase インスタンスを使うことにする。getInstance()
に名前を渡すと名前付きの Firebase インスタンスが取得できるのでそれを使用する。
PushSdk
- Project ペインの pushsdk/java/com.example.pushsdk/PushSdk をダブルクリックする。
package com.example.pushsdk;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.iid.FirebaseInstanceId;
public class PushSdk {
public static void initialize(Context context) {
FirebaseOptions options = new FirebaseOptions.Builder()
.setApplicationId("アプリケーション ID")
.setApiKey("dummy")
.build();
FirebaseApp.initializeApp(context, options, "pushsdk");
context.startService(new Intent(context, PushRegistrationService.class));
}
}
- ”アプリケーション ID” のところは「サーバーキーと送信者 ID の取得」でメモした実際のアプリケーション ID を入力することに注意。
解説: PushRegistrationService で使用する名前付きの Firebase インスタンスを初期化する必要がある。Firebase.initializeApp()
に文字列を渡すと、デフォルトではなく名前付きの Firebase インスタンスが初期化できる。
Firebase プロジェクトの作成
プッシュライブラリが使う Firebase プロジェクトとは別の、サンプルアプリが利用する Firebase プロジェクトを Firebase コンソールで作成する。
- https://console.firebase.google.com/ を開く。
- [+プロジェクトを追加] をクリックする。
- [プロジェクト名] に "my-push-migration-sampleapp" を入力する。
- [国 / 地域] から "日本" を選択する。
- [プロジェクトを作成] をクリックする。
- [次へ] をクリックする。
Firebase にアプリを追加
- https://console.firebase.google.com/ を開く。
- プロジェクト "my-push-migration-sampleapp" をクリックする。
- [Android アプリに Firebase を追加する] をクリックする。
- [Android パッケージ名] に "com.example.pushmigration" を入力する。
- [アプリの登録] をクリックする。
- [ダウンロード google-services.json] をクリックする。
- [続行] をクリックする。
- [終了] をクリックする。
解説: ここで追加するアプリのパッケージ名は、組み込もうとしているアプリのパッケージ名と一致していなければならない。
サンプルアプリの実装
Firebase の追加
- ダウンロードした google-services.json を app ディレクトリに置く。
- Project ペインの Gradle Scripts/build.gradle (Project: your-project-name) をダブルクリックする。
- buildscript.dependencies に "
classpath 'com.google.gms:google-services:3.1.1'
" を入力する。
buildscript {
// ...
dependencies {
// ...
classpath 'com.google.gms:google-services:3.1.1'
}
// ...
- 上部に現れる [Sync Now] をクリックする。
- Project ペインの Gradle Scripts/build.gradle (Module: app) をダブルクリックする。
- dependencies に "
compile 'com.google.firebase:firebase-core:11.8.0'
" を入力する。 - ファイルの末尾に "
apply plugin: 'com.google.gms.google-services'
" を追加する。
// ...
dependencies {
// ...
compile project(":pushsdk")
compile 'com.google.firebase:firebase-core:11.8.0'
}
apply plugin: 'com.google.gms.google-services'
- 上部に現れる [Sync Now] をクリックする。
サンプルアプリの実行
- メニューから [Run | Run 'app'] を選択する。
- Logcat ペインを開く。
-
/com.example.pushmigration I/pushsdk: XxXxXXxxX...
のように登録 ID が表示されているはず。 - この登録 ID に宛ててプッシュメッセージを送信してみる。
$ export SVRKEY='サーバーキー' REGID='登録 ID'
$ cat <<'^Z' | bash
curl -H "Authorization: key=$SVRKEY" -H 'Content-Type: application/json' \
-d '{"registration_ids": [ "'$REGID'" ], "data": { "message": "Hello Android" }}' \
https://fcm.googleapis.com/fcm/send
^Z
- Logcat ペインに
/com.example.pushmigration I/pushsdk: from:送信者 ID, message: Hello Android
と表示されるはず(初回など数分遅れて表示されることがあるので待ってみる)。
解説: FCM 版と変わりなし。
この最後の版で、ライブラリを組み込む本体アプリが Firebase を組み込んでいる場合にも組み込んでいない場合にも対応できる。
Amazon SNS による送信
実際のプッシュ配信は Amazon SNS などのサービス経由で実行されることも多い。ここでは Amazon SNS を利用していた場合の FCM の利用について検証してみる。
SNS 設定
- AWS にログインし、SNS コンソールを開く。
- [Application] をクリックする。
- [Create platform application] をクリックする。
- [Application name] に "my-push-migration" を入力する。
- [Push notification] で "Google Cloud Messaging (GCM)" を選択する。
- [API key] に「サーバーキーと送信者 ID の取得」でメモした実際のサーバーキーを入力する。
- [Create platform application] をクリックする。
メッセージを送ってみる
- AWS にログインし、SNS コンソールを開く。
- [Application] をクリックする。
- "my-push-application" のチェックボックスを選択する。
- [Create platform endpoint] をクリックする。
- Android Studio でサンプルアプリを起動する。
- Logcat ペインに表示される
/com.example.pushmigration I/pushsdk: XxXxXXxxX...
から登録 ID を読み取り [Device token] に入力する。 - [Add endpoint] をクリックする。
- "my-push-application" の [ARN] をクリックする。
- 先ほど作成した endpoint のチェックボックスを選択する。
- [Publish to endpoint] をクリックする。
- [Message format] の [JSON] をクリックする。
- [JSON message generator] をクリックする。
- [Message] に "
Hello Android from AWS
" を入力する。 - [Publish Message] をクリックする。
- Android Studio の Logcat ペインに "
/com.example.pushmigration I/pushsdk: from:送信者 ID, message: Hello Android from AWS
" と表示される。
解説: Amazon SNS では GCM 版と FCM 版の区別は現在のところ無い。Google の API サーバーが GCM API でも FCM API でも区別なく受け入れるようになっているからだと思われる。ただし、Google Cloud Platform の古い GCM API キーを使っていた場合(今回これは再現できなかった)は、その API キーを、Firebase プロジェクトに移行した際に生成された長いサーバーキーに入れ替える必要があるだろう。
問題点など
- 今回は Android Studio (というより gradle)の
dependencies
にcompile project("xxx")
を書いてライブラリを組み込む方法で行った。この方法だと、ライブラリが依存するライブラリが読み込まれたり、ライブラリ自体のマニフェストやライブラリが依存するライブラリのマニフェストが組み込むアプリのマニフェストにマージされたりしたが、.aar
や.jar
を提供する形でそれがどうなるかを検証していない点がやや不備である。 - 今回のライブラリの提供形態でも、ライブラリの
dependencies
にcompile 'com.google.firebase:firebase-messaging:11.0.4'
と書き、本体アプリのdependencies
にcompile 'com.google.firebase:firebase-core:11.8.0'
と書く(バージョンが異なることに注意)とアプリがクラッシュした。このままではライブラリの提供形態としては不備である(Firebase のバージョン別にライブラリを提供する必要が生じる)。
参考文献
- Migrate a GCM Client App for Android to Firebase Cloud Messaging | Cloud Messaging | Google Developers
- Androidの新プッシュ通知FCMへの移行 : 時々、失業SEの開発日誌
- 複数のプロジェクトを設定する | Firebase
- The Firebase Blog: How does Firebase initialize on Android?
- Google Developers Japan: Android での Firebase の初期化を使いこなす
- レガシー アプリサーバーのプロトコルを使用してメッセージを送信する | Firebase