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

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

この記事を読むのに必要な時間:およそ 9 分

フォアグラウンド・バッググラウンド時の挙動の違い

実はメッセージ受信時に必ず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 宛のメッセージを受信しません。

著者プロフィール

白山文彦(しろやまふみひこ)

サーバサイド,インフラ,Androidなど何でもやるプログラマ。