Google I/Oで発表されてから盛り上がっているFirebase
GCMからFirebaseに乗り換える方向けに移行方法、テスト方法をまとめたものです。
https://firebase.google.com/
FCMはすごく簡単
gcm.jarの頃
- Registration IDが空になったら再発行処理が必要だった
play-services-gcmに移行したての頃
- gcm.jarでは内部でやってくれていた、アプリバージョンをチェックして再発行などの再登録処理を自前で用意
FCM
- SDK内ですべてやってくれる
移行メリット
- 移行コストが低い。シンプル
- 基本的にサーバーサイド変更不要
- デバイス登録処理やトークン発行処理は自動でやってくれる
- 今後のプッシュ関連の機能追加はFCMで行われる
- サーバーレスも可能
- Firebase Analyticsと連携した配信も可能
- GCMはいつか止まる
移行方法
gcm.jarを使ってる場合
とりあえず、gcm.jarを消して↓を見てください
Github firebase/quickstart-android
そのあとに、ここを見ると良いです。
Set Up a Firebase Cloud Messaging Client App on Android
play-services-gcmを使ってる場合
基本的にはこの通りやるだけ
Migrate a GCM Client App for Android to Firebase Cloud Messaging
- AndroidManifest.xmlはたったの2つのServiceを追加するだけ
- Permissionはなんと不要!(※ライブラリ側のAndroidManifest.xmlに記述されているため)
- 移行するとコード減ります。
#確認方法など
トークン発行タイミング
- 初回起動時に自動でFCM SDKがregistration tokenを発行してくれる。
- tokenが変わるたびに
FirebaseInstanceIdService
のonTokenRefresh()
で最新のtokenを取得できるのでサーバーに登録したりする
@Override
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
// TODO: Implement this method to send any registration to your app's servers.
sendRegistrationToServer(refreshedToken);
}
InstanceIDが変わった時のテスト
InstanceIDが変わる条件はいくつかあるようです。
- アプリ内でInstanceIDを削除した時
- 新しいデバイスで復元された時
- アンインストール/再インストール
- アプリデータを削除
adb shell am startservice -a com.google.android.gms.iid.InstanceID --es "CMD" "RST" -n your.package.name/your.own.MyInstanceIDListenerService
↑の方法でもonTokenRefresh()が呼ばれなかったので、DeleteTokenServiceを仕込んで呼ぶことにしてみます。
$ adb shell am startservice your.packeage.name/your.own.service.DeleteTokenService
すると、onTokenRefresh()が呼ばれることが確認できました。
DeleteTokenServiceは内部でFirebaseInstanceId.getInstance().deleteInstanceId();
を呼んでるため、過去に発行されたregistrationId
も無効にります。
ユーザーへの影響の確認
GCMからFCMに移行できたけど、アップデートの検証もしたい所。
- 現在ストアに上がってるGCM版のAPKを用意
- FCMに移行したバージョンのAPKを用意
// 事前にonTokenRefresh()の中でログを仕込んでおく(今回はtokenというタグ)
$ adb logcat | grep token
// GCM版をインストールしたら一度起動する
$ adb install gcm.apk
// 新しいバージョンを入れて、onTokenRefresh()内に仕込んだtokenログが出力されていることを確認する
$ adb install -r fcm.apk
サーバーに新しいデバイストークンを送ってる場合、トークンが書き換わってるかなども確認しておきましょう。
注意
Firebaseプロジェクト作成時
すでにGoogleプロジェクトがある場合、Firebaseで新規プロジェクトを作らないこと。
apiキーが違うのでいろいろトラブルが起きます。
Googleプロジェクトをインポートから追加してください。
(AmazonSNSを利用しているプロダクトで、すでにGoogleプロジェクトと連携させている状態で、Firebase Analyticsを新規で導入したところ、APIキーが合わずプッシュが来なくなる問題が発生しました。)
FirebaseMessagingService内でHandlerを使う時
gcm.jarの頃、GCMBaseIntentService
はIntentService
を継承していたため、new Handler()
が使えました。
今回はFirebaseMessagingService
はService
を継承しているため、そのままnew Handler()
をしようとするとRuntimeException
が発生します。
java.lang.RuntimeException
Can't create handler inside thread that has not called Looper.prepare()
if (Looper.myLooper() == null) {
Looper.prepare();
}
エラーがでたら
java.lang.IllegalStateException: FirebaseApp with name [DEFAULT] doesn't exist.
// これが追加されてるか確認
apply plugin: 'com.google.gms.google-services'
期待したいこと
実装方法がだいぶ簡単になったので、しばらくは大きく変わることは無いでしょう。(希望)
これからはプッシュ配信運用で便利な機能追加を期待しています。
定期配信ができるとか。
(あと複数Token発行してしまっても、自動で古い方をオフに出来るとか...)
参考
When will InstanceIDListenerService be called and how to test it?
FCM Vs GCM? Why we need to migrate from GCM to FCM
Firebase FCM force onTokenRefresh() to be called
FirebaseApp with name [DEFAULT] doesn't exist
Firebase FCM force onTokenRefresh() to be called
DeleteTokenService
http://gohowall.com/firebase-fcm-force-ontokenrefresh-to-be-called/
public class DeleteTokenService extends IntentService
{
public static final String TAG = DeleteTokenService.class.getSimpleName();
public DeleteTokenService()
{
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent)
{
try
{
// Check for current token
String originalToken = getTokenFromPrefs();
Log.d(TAG, "Token before deletion: " + originalToken);
// Resets Instance ID and revokes all tokens.
FirebaseInstanceId.getInstance().deleteInstanceId();
// Clear current saved token
saveTokenToPrefs("");
// Check for success of empty token
String tokenCheck = getTokenFromPrefs();
Log.d(TAG, "Token deleted. Proof: " + tokenCheck);
// Now manually call onTokenRefresh()
Log.d(TAG, "Getting new token");
FirebaseInstanceId.getInstance().getToken();
}
catch (IOException e)
{
e.printStackTrace();
}
}
private void saveTokenToPrefs(String _token)
{
// Access Shared Preferences
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = preferences.edit();
// Save to SharedPreferences
editor.putString("registration_id", _token);
editor.apply();
}
private String getTokenFromPrefs()
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
return preferences.getString("registration_id", null);
}
}
メモ
これを書くのに2時間も掛かった。。。
段落の使い方が理解できてない
もっと質の良いアウトプットをしていこう。