今回も前回 に引き続き、Google I/0 2016で発表された新しいFirebaseの機能の中から筆者が特に重要と考える機能をピックアップして実際にアプリに組み込む方法をコード例を交えてご紹介したいと思います。
今回は、Googleの新しいメッセージ配信基盤として従来のGoogle Cloud MessagingをFirebaseブランドに取り込んで生まれ変わった次の2つの機能を解説します。
Firebase Cloud Messaging
Firebase Notifications
Firebase Cloud MessagingとFirebase Notificationsの違い
Firebase Cloud MessagingとFirebase Notificationsはお互いに無関係な機能ではなく密接な関わりがあります。
Firebase Cloud MessagingはAndroidのみならずiOS、Webなどクロスプラットフォームにメッセージ(たとえばプッシュ通知)を効率的に配信するための基盤となるシステムです。Firebase Cloud Messagingサーバ群はHTTPやXMPPをサポートしており、すでに運用している自社サービス等と柔軟に連携しながらメッセージを配信することができます。
図1 Firebase Cloud Messagingの概要
illustration by Google
Firebase NotificationsはこのFirebase Cloud MessagingをWeb越しから使うための便利なコンソールという位置づけです。Firebase Notificationsを利用するとFirebase Cloud Messagingを制御するために自前でサーバを用意する必要もありませんし、1行のコードを書く必要もなく、わかりやすいWebコンソールから簡単にユーザにメッセージを配信することができます。
Firebase Cloud Messagingのメッセージタイプ
Firebase Cloud Messagingには2種類のメッセージタイプが存在します。メッセージタイプによって受信したときの取り扱い方法が違うので、この違いを理解しておくことは非常に重要です。
次の表にメッセージタイプとその違いを示します。
メッセージタイプ 概要 ペイロードサイズ
Notification Message いわゆるプッシュ通知としてユーザ端末で表示されることを意図されたメッセージタイプ。title
やbody
など、プッシュ通知でよく使われるキーがあらかじめ定義されている。 2KB Data Message 利用者が任意のキーバリューペアを設定できる。どのような値をセットするかも自由であるし、どのように利用するかもユーザに任されている。これをプッシュ通知として利用しても何ら問題ない。 4KB
Notification Message
前出の表のとおり、プッシュ通知として利用されることを想定されたメッセージタイプです。title
、body
、icon
などのプッシュ通知でよく使われるキーがあらかじめ定義されており、この形式に則ってメッセージを作成します。
メッセージは次のようなJSONで表現されます。
{
"to" : "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...", // 送信相手
"notification" : {
"body" : "great match!",
"title" : "Portugal vs. Denmark",
"icon" : "myicon"
}
}
このように、Notification Messageではnotification
というキーの下に定義済みのキーとセットになる値を指定します。この例ではPortugal vs. Denmark
というタイトルでgreat match!
という本文のプッシュ通知がmyicon
というアイコンで表示されます。
後のAndroidのコード例でも紹介しますが、Notification Messageとして送信しておけば、Android側でほとんど1行もコードを書かなくてもバックグラウンドで通知を受診時にNotificationを表示してくれたりと便利なメッセージタイプです。その代わり定義済みのキー以外は利用できず、またペイロードサイズ(データサイズ)も2KBと小さめです。
定義済みのキーの一例は次の表のとおりです。
キー 概要 備考
title 通知のタイトル 必須
body 通知の本文 任意
sound 通知のサウンド 任意
icon 通知のアイコン Androidのみ。任意
badge バッジ iOSのみ。任意
定義済みのキーは他にもありますので、詳しくは公式サイト をご参照ください。
Data Message
Notification Messageでは指定できるキーがあらかじめ定められていましたが、Data Messageでは完全に任意のキーバリューペアをペイロードサイズの許す限りいくらでも指定することができます。たとえば次のJSONのようになります。
{
"to" : "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...", // 送信相手
"data" : {
"Nick" : "Mario",
"body" : "great match!",
"Room" : "PortugalVSDenmark"
},
}
このように、Data Messageではdata
というキーの下に任意のキーバリューペアを好きなだけ設定することができます。その代わり、このメッセージを受け取ったクライアントではこのデータのハンドリングはユーザに完全に委ねられます。自動的にNotificationを表示してくれたりはしないのでもしData Messageをプッシュ通知として利用したいのであれば明示的にそのようにプログラムを書く必要があります。
どちらを利用すべきか
これは次節のAndroidコード例で詳しく解説しますが、メッセージタイプと受信したアプリの状態(フォアグラウンドかバックグラウンドか)によってメッセージ受信時の挙動とハンドリング方法が異なるので、一概にどちらがおすすめとは言い切れませんが、すべてを細かく制御したい場合はData Messageの方が向いています。詳しくは次節をご覧ください。
Android クライアントでプッシュ通知を受け取る
Firebase Cloud Messagingの概要とメッセージタイプがわかったところで、実際にAndroidでプッシュ通知を受け取ってNotificationを表示する方法を紹介したいと思います。
セットアップ
AndroidでFirebase Cloud Messagingを利用するにはアプリケーションモジュールのbuild.gradle
に次のdependencyを追加するだけです。
dependencies {
compile 'com.google.firebase:firebase-messaging:9.2.0'
}
Registration Tokenの取得
Firebase Cloud Messagingではメッセージを送信する対象のユーザをRegistration Tokenという識別子で区別します。このRegistration Tokenを指定することであるユーザだけにピンポイントでメッセージを送るといったことが可能になります。
Registration Tokenは前述のセットアップが完了するとアプリインストール時に自動的に生成されるので、ユーザが特に生成コードを呼び出す必要はありません。現在のRegistration Tokenにアクセスするには次のようにします。
String registrationToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "InstanceID token: " + registrationToken);
実際のアプリケーションでは、自社サービスのユーザ間でメッセージ送り合った際にプッシュ通知で知らせるなど、自社サーバで各ユーザのRegistration Tokenを保持しておいて、API越しにFirebase Cloud Messagingと連携するようなケースが非常に多いと思われます。そのような用途のために、ここで取得したRegistration Tokenを適宜サーバに送るコードを追加すると良いでしょう。
Registration Tokenの更新
Registration Tokenは、アプリの再インストールやアプリケーションキャッシュのクリアなど、いくつかのケースで再生成されることがあります。前述のように自社サーバでRegistration Tokenを保持している場合は、このようなTokenの再生成時に情報を更新する必要があります。
Registration Tokenの更新はFirebaseInstanceIdService
の継承クラスでonTokenRefresh()
メソッドをオーバーライドすることで、更新時に必ずコールバックを受け取ることができます。
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;
public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {
@Override
public void onTokenRefresh() {
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
}
}
先ほどの例と同様、新しく取得したRegistration Tokenは適宜自社サーバに送信して利用しましょう。
また、作成したServiceはAndroidManifest.xml
に忘れずに追加するようにしましょう。
<service
android:name=".MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
プッシュ通知のハンドリング
いよいよプッシュ通知を受け取ってNotificationを表示してみたいと思います。
プッシュ通知はFirebaseMessagingService
の継承クラスで、onMessageReceive()
メソッドをオーバーライドすることでハンドリングすることができます。Firebase Cloud MessagingではNotification MessageとData Messageの2つのメッセージタイプがあることを紹介しましたが、それぞれでハンドリング方法が異なるので順番に解説したいと思います。
Notification Messageのハンドリング
Notification Messageを受信したことはRemoteMessage#getNotification()
が非null値になることで判別することができます。次のコードをご覧ください。
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
public class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "From: " + remoteMessage.getFrom());
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Notification Message");
String title = remoteMessage.getNotification().getTitle();
Log.d(TAG, "Notification Message Title: " + title);
String body = remoteMessage.getNotification().getBody();
Log.d(TAG, "Notification Message Body: " + body);
}
}
}
このように、remoteMessage.getNotification() != null
をチェックしてからremoteMessage.getNotification().getTitle()
でタイトルを、remoteMessage.getNotification().getBody()
で本文を取り出しています。
Data Messageのハンドリング
Data Messageを受信したことはRemoteMessage#getData()
が非null値になることで判別することができます。次のコードをご覧ください。
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import java.util.Map;
public class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.d(TAG, "From: " + remoteMessage.getFrom());
if (remoteMessage.getData() != null) {
Log.d(TAG, "Data Message");
Map<String, String> data = remoteMessage.getData();
String customTitle = data.get("custom_title");
Log.d(TAG, "Notification Message Title: " + customTitle);
String customBody = data.get("custom_body");
Log.d(TAG, "Notification Message Body: " + customBody);
}
}
}
こちらも同様にremoteMessage.getData() != null
をチェックしてからremoteMessage.getData()
でMap<String, String>
を取り出して利用しています。Data Messageは任意のキーバリューペアを指定できるので、キーは間違いなく存在するものを指定する必要があるので注意が必要です。
なお、このServiceも忘れずにAndroidManifest.xml
に追加するようにしましょう。
<service
android:name=".MyFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
フォアグラウンド・バッググラウンド時の挙動の違い
実はメッセージ受信時に必ずFirebaseMessagingService#onMessageReceived()
が呼び出されるとは限りません。メッセージタイプと、アプリがフォアグラウンドかバックグラウンドかで挙動が異なります。挙動の違いは次の表のようになります。
アプリの状態 Notification Messageの場合 Data Messageの場合 両方の場合
フォアグラウンド onMessageReceived onMessageReceived onMessageReceived
バックグラウンド System Tray onMessageReceived NotificationはSystem Tray、DataはPendingIntentのExtra
Notification Messageの場合
Notification Messageの場合、アプリがフォアグラウンド、つまり現在ユーザがアプリを最前面で表示している場合は、onMessageReceived()
が呼び出されます。この際、特にNotificationは表示されません 。
反対に、アプリがバックグラウンドにまわっている際にNotification Messageを受け取るとSystem Tray
というところに通知されます。これは何かというと、ユーザが1行もコードを追加しなくてもNotificationを作成してくれ、そのNotificationをタップするとアプリを自動的に起動してくれます。
図2 System Trayが自動的にNotificationを出してくれる
図3 タップすると自動的にアプリが立ち上がる
Data Messageの場合
Notification Messageの場合、アプリがフォアグラウンドにいてもバックグラウンドにいても必ずonMessageReceived()
が呼び出されます。ただしNotification Messageのようにバックグラウンド時には勝手にNotificationを用意してくれたりはしないので、Notificationを表示したい場合はプログラマが明示的にNotificationCompat
とPendingIntent
等を使ってユーザに通知してあげる必要があります。
次のコードをご覧ください。
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
if (remoteMessage.getData() != null) {
Log.d(TAG, "Data Message");
Map<String, String> data = remoteMessage.getData();
String title = data.get("custom_title");
String body = data.get("custom_body");
sendNotification(title, body);
}
}
private void sendNotification(String title, String body) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_stat_ic_notification)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
このようにsendNotification()
というメソッドを追加し、中でPendingIntent
やNotification
を自分で用意して端末に通知しています。
少々煩雑な分すべてを自分で細かく制御できるので、場合によってはData Messageだけを利用するという方針にしても良いかもしれません。
Notification MessageとData Messageの両方を指定した場合
メッセージは次のようにnotification
とdata
の両方を一度に指定することも可能です。
{
"to" : "APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx...",
"notification" : {
"body" : "great match!",
"title" : "Portugal vs. Denmark",
"icon" : "myicon"
},
"data" : {
"Nick" : "Mario",
"Room" : "PortugalVSDenmark"
}
}
この場合、フォアグラウンドでメッセージを受信した場合はNotification MessageもData Messageも両方onMessageReceived
に通知されますが、バッググラウンド時にはNotification MessageはSystem Tray
に通知され、Data Messageは通知タップ時に起動されるPendingIntent
のgetExtras()
の中に渡されるという少々わかりづらい仕様となっています。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (getIntent().getExtras() != null) {
for (String key : getIntent().getExtras().keySet()) {
String value = getIntent().getExtras().getString(key);
Log.d(TAG, "Key: " + key + " Value: " + value);
}
}
特にそうする理由がないのであれば、どちらか一方を利用するほうがわかりやすいのではないかと個人的には考えています。
トピックのサブスクライブ・アンサブスクライブ
以上でAndroidクライアントからFirebase Cloud Messagingでメッセージを受信する方法はひととおり確認できましたが、実際にメッセージを受信してみる前に、トピックという概念をご紹介したいと思います。
Firebase Cloud Messagingでは、Registration Tokenを使って特定のユーザにピンポイントにメッセージを送信する方法の他に、トピックというものを利用してグループ単位でメッセージを送信できる便利な機能があります。
トピックはユーザが任意で定めることができ、たとえば全ユーザが所属する/all
というトピックや、特定のチャットルームを表現する/rooms1234
というようなトピックを自由に購読・購読解除することができます。
ユーザがトピックをサブスクライブするコードは次のようになります。
FirebaseMessaging.getInstance().subscribeToTopic("news")
この例では /news
というトピックをサブスクライブしています。もしメッセージがこのトピック宛に送信された場合、このトピックをサブスクライブしているユーザにだけメッセージが配信されます。
メッセージのアンサブスクライブも同様です。
FirebaseMessaging.getInstance().unsubscribeFromTopic("news");
以後、このユーザは /news
宛のメッセージを受信しません。
Firebase Notifications を使ってメッセージを送信
大変長らくお待たせしました。すべての準備が整ったので、Firebase Notificationsを使ってユーザにメッセージを送信してみましょう。
FirebaseのWebコンソール からプロジェクトを選択し、Notificationsを選択してください。
図4 Firebase Notifications
「最初のメッセージを送信」を選択します。
図5 最初のメッセージを送信
「メッセージ文」に本文を入力し、その他の項目も画像のように選択します。
図6 メッセージ文の入力と送信オプションの選択
「詳細オプション」からタイトルを入力し、「 メッセージを送信」を選択します。
図7 タイトル入力/メッセージの送信
メッセージを確認して「送信」を選択します。
図8 最終確認/送信
その後、Android端末でメッセージが受信できれば成功です。
ユーザセグメントで絞り込んで送信
「ターゲット」を「ユーザセグメント」にすると、アプリや言語、バージョンなどで簡単にユーザをセグメント分けしてメッセージを送り分けることができます。
図9 ユーザセグメント
トピック単位で送信
「トピックのサブスクライブ・アンサブスクライブ」で解説したトピックをいずれかのユーザがサブスクライブしていた場合、そのトピックに対してメッセージを送信することができます。
トピックをサブスクライブしてからWebコンソールに反映されるまでには最大1日かかると説明されているので、もし反映されない場合は数時間待ってみてください。
図10 トピック
単一の端末に送信
「Registration Token」で解説したRegistration Tokenを使って、単一のユーザにピンポイントでメッセージを送信することができます。
図11 単一の端末
Firebase Notifications の制限事項
Firebase Notificationsのメッセージタイプは自動的にNotification Messageになりますが、現状の制限事項として、バックグラウンドでメッセージを受信した際にiconを設定していてもNotificationのアイコンが強制的にアプリのランチャーアイコンになってしまう という現象が発生しています。今後改善されるかどうかは不明ですが、2016年7月現在これは仕様のようなので注意してください。
次節で解説するAPIを利用すると、Notification Messageの場合でも正しくiconを指定してメッセージを送信することができます。
APIを使ってメッセージを送信する
これまでも触れてきたように、Firebase Cloud Messagingは自前で持っているサーバと連携してこそ、その真価を発揮します。
自前のサーバとの連携方法はAbout Firebase Cloud Messaging Server に詳しく解説されています。紹介すると長くなるため本連載では扱いませんが、ちょっと試してみるだけならcurl
コマンドでAPI越しにメッセージを送信することが可能です。
基本的なアイディアはこのcurl
コマンドと同じなので理解の助けになる他、開発時にちょっとメッセージを送ってみるといった場合に大変重宝するので解説したいと思います。
サーバーキーの取得
FirebaseのWebコンソールにログインし、「 プロジェクトの設定」を選択します。
図12 プロジェクトの設定
タブを「クラウドメッセージング」に切り替え、「 サーバーキー」をクリップボードにコピーします。
図13 サーバーキーの取得
Notification Messageの送信
それではさっそくサーバーキーを利用して、curl
コマンドでNotification Messageを送信してみましょう。
curl --header "Authorization: key=[YOUR_SERVER_KEY]" \
--header Content-Type:"application/json" \
https://fcm.googleapis.com/fcm/send \
-d "{\"to\": \"/topics/news\",\"priority\":\"high\",\"notification\": {\"title\": \"this is title\", \"body\": \"this is body\", \"icon\": \"ic_stat_ic_notification\"}}"
--header "Authorization: key=[YOUR_SERVER_KEY]"
の部分に先ほど取得したサーバーキーを指定します。あとはhttps://fcm.googleapis.com/fcm/send
というエンドポイントにapplication/json
としてメッセージを送信しているだけです。
JSONの中では"to":"/topics/news"
という指定で/news
というトピックに対してメッセージを送信しています。"priority":"hight"
で高プライオリティを指定しています。プライオリティに関してはSetting the priority of a message をご参照ください。
ここで最も重要なのはその次の"notification":{}
の部分です。ここでtitle
とbody
とicon
を指定してメッセージを送信しています。無事クライアントで受信できれば成功です。
Data Messageの送信
同様にData Messageも送信してみましょう。
curl --header "Authorization: key=[YOUR_SERVER_KEY]" \
--header Content-Type:"application/json" \
https://fcm.googleapis.com/fcm/send \
-d "{\"to\": \"/topics/news\",\"priority\":\"high\",\"data\": {\"custom_title\": \"this is custom title\", \"custom_body\": \"this is custom body\", \"icon\": \"ic_stat_ic_notification\"}}"
メッセージをdata
として送信した以外はNotification Messageとまったく同様です。こちらはキーバリューペアに任意のものを設定することができます。こちらもクライアントで受信できれば成功です。
まとめ
いかがだったでしょうか。
今回はFirebaseの新機能のうち、メッセージ配信基盤としてGoogle Cloud Messagingを置き換えるサービスとして生まれ変わったFirebase Cloud Messagingについてご紹介しました。非常に簡単かつ柔軟にメッセージ配信を行うことができることをおわかりいただけたのではないでしょうか。
さて、次回はこれまでの総括として、連載で紹介してきたさまざまな機能を盛り込んだ実践的なサンプルアプリを公開し、そのソースコードを解説して連載を締めたいと思います。どうぞお楽しみに。