スマホアプリ開発を加速する、Firebaseを使ってみよう

第9回Firebase Cloud MessagingとFirebase Notificationsでメッセージを送信する

今回も前回に引き続き、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の概要
図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いわゆるプッシュ通知としてユーザ端末で表示されることを意図されたメッセージタイプ。titlebodyなど、プッシュ通知でよく使われるキーがあらかじめ定義されている。2KB
Data Message利用者が任意のキーバリューペアを設定できる。どのような値をセットするかも自由であるし、どのように利用するかもユーザに任されている。これをプッシュ通知として利用しても何ら問題ない。4KB

Notification Message

前出の表のとおり、プッシュ通知として利用されることを想定されたメッセージタイプです。titlebodyiconなどのプッシュ通知でよく使われるキーがあらかじめ定義されており、この形式に則ってメッセージを作成します。

メッセージは次のような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の場合両方の場合
フォアグラウンドonMessageReceivedonMessageReceivedonMessageReceived
バックグラウンドSystem TrayonMessageReceivedNotificationはSystem Tray、DataはPendingIntentのExtra

Notification Messageの場合

Notification Messageの場合、アプリがフォアグラウンド、つまり現在ユーザがアプリを最前面で表示している場合は、onMessageReceived()が呼び出されます。この際、特にNotificationは表示されません

反対に、アプリがバックグラウンドにまわっている際にNotification Messageを受け取るとSystem Trayというところに通知されます。これは何かというと、ユーザが1行もコードを追加しなくてもNotificationを作成してくれ、そのNotificationをタップするとアプリを自動的に起動してくれます。

図2 System Trayが自動的にNotificationを出してくれる
図2 System Trayが自動的にNotificationを出してくれる
図3 タップすると自動的にアプリが立ち上がる
図3 タップすると自動的にアプリが立ち上がる

Data Messageの場合

Notification Messageの場合、アプリがフォアグラウンドにいてもバックグラウンドにいても必ずonMessageReceived()が呼び出されます。ただしNotification Messageのようにバックグラウンド時には勝手にNotificationを用意してくれたりはしないので、Notificationを表示したい場合はプログラマが明示的にNotificationCompatPendingIntent等を使ってユーザに通知してあげる必要があります。

次のコードをご覧ください。

@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()というメソッドを追加し、中でPendingIntentNotificationを自分で用意して端末に通知しています。

少々煩雑な分すべてを自分で細かく制御できるので、場合によってはData Messageだけを利用するという方針にしても良いかもしれません。

Notification MessageとData Messageの両方を指定した場合

メッセージは次のようにnotificationdataの両方を一度に指定することも可能です。

{
  "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は通知タップ時に起動されるPendingIntentgetExtras()の中に渡されるという少々わかりづらい仕様となっています。

@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
図4 Firebase Notifications

「最初のメッセージを送信」を選択します。

図5 最初のメッセージを送信
図5 最初のメッセージを送信

「メッセージ文」に本文を入力し、その他の項目も画像のように選択します。

図6 メッセージ文の入力と送信オプションの選択
図6 メッセージ文の入力と送信オプションの選択

「詳細オプション」からタイトルを入力し、⁠メッセージを送信」を選択します。

図7 タイトル入力/メッセージの送信
図7 タイトル入力/メッセージの送信

メッセージを確認して「送信」を選択します。

図8 最終確認/送信
図8 最終確認/送信

その後、Android端末でメッセージが受信できれば成功です。

ユーザセグメントで絞り込んで送信

「ターゲット」「ユーザセグメント」にすると、アプリや言語、バージョンなどで簡単にユーザをセグメント分けしてメッセージを送り分けることができます。

図9 ユーザセグメント
図9 ユーザセグメント

トピック単位で送信

「トピックのサブスクライブ・アンサブスクライブ」で解説したトピックをいずれかのユーザがサブスクライブしていた場合、そのトピックに対してメッセージを送信することができます。

トピックをサブスクライブしてからWebコンソールに反映されるまでには最大1日かかると説明されているので、もし反映されない場合は数時間待ってみてください。

図10 トピック
図10 トピック

単一の端末に送信

「Registration Token」で解説したRegistration Tokenを使って、単一のユーザにピンポイントでメッセージを送信することができます。

図11 単一の端末
図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 プロジェクトの設定
図12 プロジェクトの設定

タブを「クラウドメッセージング」に切り替え、⁠サーバーキー」をクリップボードにコピーします。

図13 サーバーキーの取得
図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":{}の部分です。ここでtitlebodyiconを指定してメッセージを送信しています。無事クライアントで受信できれば成功です。

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についてご紹介しました。非常に簡単かつ柔軟にメッセージ配信を行うことができることをおわかりいただけたのではないでしょうか。

さて、次回はこれまでの総括として、連載で紹介してきたさまざまな機能を盛り込んだ実践的なサンプルアプリを公開し、そのソースコードを解説して連載を締めたいと思います。どうぞお楽しみに。

おすすめ記事

記事・ニュース一覧