複数のデバイスにメッセージを送信する

Firebase Cloud Messaging(FCM)では、次の 2 つの方法で複数のデバイスにメッセージを送信できます。

このチュートリアルでは、Admin SDK または REST API を使用してアプリサーバーから FCM にトピック メッセージを送信し、それらのメッセージを Android アプリで受信して処理する手順を取り上げます。バックグラウンドとフォアグラウンドの両方のアプリのメッセージ処理について、設定から検証まですべての手順を説明します。

SDK を設定する

Android クライアント アプリでの FCM の設定または最初のメッセージを送信する手順がすでに済んでいる場合は、このセクションの一部の手順を省略できます。

始める前に

  • Android Studio の最新バージョンをインストールするか、更新します。

  • プロジェクトが次の要件を満たしていることを確認します。

    • ターゲットが API レベル 19(KitKat)以上であること。
    • Android 4.4 以上を使用していること。
    • Jetpack(AndroidX)を使用していること。次のバージョン要件を満たしている必要があります。
      • com.android.tools.build:gradle v3.2.1 以降
      • compileSdkVersion 28 以降
  • 実機を設定するか、エミュレータを使用してアプリを実行します。
    Google Play 開発者サービスに依存している Firebase SDK を使用する場合、デバイスまたはエミュレータに Google Play 開発者サービスがインストールされている必要があります。

  • Google アカウントを使用して Firebase にログインします。

Android プロジェクトがない場合、Firebase プロダクトを試してみるだけであれば、クイックスタート サンプルをダウンロードしてお使いいただけます。

Firebase プロジェクトを作成する

Android アプリに Firebase を追加する前に、Android アプリに接続するための Firebase プロジェクトを作成します。Firebase プロジェクトの詳細については、Firebase プロジェクトについて理解するをご覧ください。

アプリを Firebase に登録する

Android アプリで Firebase を使用するには、アプリを Firebase プロジェクトに登録する必要があります。アプリの登録は、プロジェクトへのアプリの「追加」とも呼ばれます。

  1. Firebase コンソールに移動します。

  2. プロジェクトの概要ページの中央で、Android アイコン()または [アプリを追加] をクリックして、設定ワークフローを起動します。

  3. [Android パッケージ名] フィールドにアプリのパッケージ名を入力します。

  4. (省略可)その他のアプリ情報(アプリのニックネームデバッグ用の署名証明書 SHA-1)を入力します。

  5. [アプリを登録] をクリックします。

Firebase 構成ファイルを追加する

  1. Firebase Android 構成ファイル(google-services.json)をダウンロードしてアプリに追加します。

    1. [Download google-services.json] をクリックして、Firebase Android 構成ファイルを入手します。

    2. 構成ファイルをアプリのモジュール(アプリレベル)ルート ディレクトリに移動します。

  2. google-services.json 構成ファイルの値に Firebase SDK からアクセスできるようにするには、Google サービスの Gradle プラグインgoogle-services)が必要です。

    1. ルートレベル(プロジェクト レベル)の Gradle ファイル(<project>/build.gradle)で、buildscript の依存関係として Google サービス プラグインを追加します。

      buildscript {
      
          repositories {
            // Make sure that you have the following two repositories
            google()  // Google's Maven repository
            mavenCentral()  // Maven Central repository
          }
      
          dependencies {
            ...
      
            // Add the dependency for the Google services Gradle plugin
            classpath 'com.google.gms:google-services:4.3.15'
          }
      }
      
      allprojects {
        ...
      
        repositories {
          // Make sure that you have the following two repositories
          google()  // Google's Maven repository
          mavenCentral()  // Maven Central repository
        }
      }
    2. モジュール(アプリレベル)の Gradle ファイル(通常は <project>/<app-module>/build.gradle)に、Google サービス プラグインを追加します。

      plugins {
          id 'com.android.application'
      
          // Add the Google services Gradle plugin
          id 'com.google.gms.google-services'
          ...
      }

アプリに Firebase SDK を追加する

  1. モジュール(アプリレベル)の Gradle ファイル(通常は <project>/<app-module>/build.gradle)に、Firebase Cloud Messaging Android ライブラリの依存関係を追加します。ライブラリのバージョニングの制御には、Firebase Android 部品構成表(BoM)を使用することをおすすめします。

    Firebase Cloud Messaging でのエクスペリエンスを最適化するために、Firebase プロジェクトで Google アナリティクスを有効にして、Google アナリティクス用の Firebase SDK をアプリに追加することをおすすめします。

    Java

    dependencies {
        // Import the BoM for the Firebase platform
        implementation platform('com.google.firebase:firebase-bom:32.1.0')
    
        // Add the dependencies for the Firebase Cloud Messaging and Analytics libraries
        // When using the BoM, you don't specify versions in Firebase library dependencies
        implementation 'com.google.firebase:firebase-messaging'
        implementation 'com.google.firebase:firebase-analytics'
    }

    Firebase Android 部品構成表を使用すると、アプリは常に互換性のあるバージョンの Firebase Android ライブラリを使用します。

    (代替方法)BoM を使用せずに Firebase ライブラリの依存関係を追加する

    Firebase BoM を使用しない場合は、依存関係の行でそれぞれの Firebase ライブラリのバージョンを指定する必要があります。

    アプリで複数の Firebase ライブラリを使用する場合は、すべてのバージョンの互換性を確保するため、BoM を使用してライブラリのバージョンを管理することを強くおすすめします。

    dependencies {
        // Add the dependencies for the Firebase Cloud Messaging and Analytics libraries
        // When NOT using the BoM, you must specify versions in Firebase library dependencies
        implementation 'com.google.firebase:firebase-messaging:23.1.2'
        implementation 'com.google.firebase:firebase-analytics:21.3.0'
    }

    Kotlin+KTX

    dependencies {
        // Import the BoM for the Firebase platform
        implementation platform('com.google.firebase:firebase-bom:32.1.0')
    
        // Add the dependencies for the Firebase Cloud Messaging and Analytics libraries
        // When using the BoM, you don't specify versions in Firebase library dependencies
        implementation 'com.google.firebase:firebase-messaging-ktx'
        implementation 'com.google.firebase:firebase-analytics-ktx'
    }

    Firebase Android 部品構成表を使用すると、アプリは常に互換性のあるバージョンの Firebase Android ライブラリを使用します。

    (代替方法)BoM を使用せずに Firebase ライブラリの依存関係を追加する

    Firebase BoM を使用しない場合は、依存関係の行でそれぞれの Firebase ライブラリのバージョンを指定する必要があります。

    アプリで複数の Firebase ライブラリを使用する場合は、すべてのバージョンの互換性を確保するため、BoM を使用してライブラリのバージョンを管理することを強くおすすめします。

    dependencies {
        // Add the dependencies for the Firebase Cloud Messaging and Analytics libraries
        // When NOT using the BoM, you must specify versions in Firebase library dependencies
        implementation 'com.google.firebase:firebase-messaging-ktx:23.1.2'
        implementation 'com.google.firebase:firebase-analytics-ktx:21.3.0'
    }

  2. Android プロジェクトを Gradle ファイルと同期します。

クライアント アプリをトピックにサブスクライブする

クライアント アプリは、既存のトピックにサブスクライブすることも、新しいトピックを作成することもできます。クライアント アプリを新しいトピック名(Firebase プロジェクトにまだ存在していないトピック名)にサブスクライブすると、その名前の新しいトピックが FCM に作成され、その後すべてのクライアントはそのトピックにサブスクライブできるようになります。

トピックにサブスクライブする場合、クライアント アプリは、FCM トピック名を指定して Firebase Cloud Messaging subscribeToTopic() を呼び出します。このメソッドは Task を返します。この戻り値により、完了リスナーはサブスクリプションが正常に完了したかどうかを判断できます。

Kotlin+KTX

Firebase.messaging.subscribeToTopic("weather")
    .addOnCompleteListener { task ->
        var msg = "Subscribed"
        if (!task.isSuccessful) {
            msg = "Subscribe failed"
        }
        Log.d(TAG, msg)
        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
    }

Java

FirebaseMessaging.getInstance().subscribeToTopic("weather")
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                String msg = "Subscribed";
                if (!task.isSuccessful()) {
                    msg = "Subscribe failed";
                }
                Log.d(TAG, msg);
                Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
            }
        });

サブスクライブを解除する場合、クライアント アプリは、トピック名を指定して Firebase Cloud Messaging unsubscribeFromTopic() を呼び出します。

トピック メッセージを受信して処理する

FCM は、他のダウンストリーム メッセージと同じようにトピック メッセージを配信します。

メッセージを受信するには、FirebaseMessagingService を拡張したサービスを使用します。このサービスは onMessageReceived コールバックと onDeletedMessages コールバックをオーバーライドする必要があります。メッセージは受信から 20 秒以内に処理されるようにします(Android Marshmallow では 10 秒)。時間枠は、onMessageReceived を呼び出す前に発生した OS の遅延に応じて短くなる場合があります。この時間が経過すると、Android O のバックグラウンド実行制限などの OS の各種動作によって、作業完了が妨げられる可能性があります。詳細については、メッセージの優先度の概要をご覧ください。

onMessageReceived は次の例外を除いて、ほとんどのメッセージ タイプに提供されます。

  • アプリがバックグラウンドで動作しているときに配信された通知メッセージ。この場合、通知はデバイスのシステムトレイに配信されます。ユーザーが通知をタップすると、デフォルトでアプリ ランチャーが開きます。

  • バックグラウンドで受信されたときに通知とデータ ペイロードの両方を持つメッセージ。この場合、通知はデバイスのシステムトレイに配信され、データ ペイロードはランチャー アクティビティのインテントの追加部分に配信されます。

要約:

アプリの状態 通知 データ 両方
フォアグラウンド onMessageReceived onMessageReceived onMessageReceived
バックグラウンド システムトレイ onMessageReceived 通知: システムトレイ
データ: インテントの追加部分内
メッセージ タイプの詳細については、通知とデータ メッセージをご覧ください。

アプリ マニフェストの編集

FirebaseMessagingService を使用するには、アプリ マニフェストに次の設定を追加する必要があります。

<service
    android:name=".java.MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

通知のデザインをカスタマイズするためのデフォルト値を設定することもおすすめします。通知ペイロード内にアイコンやカラーの値が設定されていない場合に適用される、カスタム デフォルト アイコンとカスタム デフォルト カラーを指定できます。

カスタム デフォルト アイコンとカスタムカラーを設定するには、次の行を application タグに追加します。

<!-- Set custom default icon. This is used when no icon is set for incoming notification messages.
     See README(https://goo.gl/l4GJaQ) for more. -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/ic_stat_ic_notification" />
<!-- Set color used with incoming notification messages. This is used when no color is set for the incoming
     notification message. See README(https://goo.gl/6BKBk7) for more. -->
<meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/colorAccent" />

Android では、以下に対してカスタム デフォルト アイコンが表示されます。

  • Notifications Composer から送信されたすべての通知メッセージ。
  • 通知ペイロード内にアイコンが明示的に設定されていない通知メッセージ。

Android では、以下に対してカスタム デフォルト カラーが使用されます。

  • Notifications Composer から送信されたすべての通知メッセージ。
  • 通知ペイロード内にカラーが明示的に設定されていない通知メッセージ。

カスタム デフォルト アイコンが設定されず、通知ペイロード内にもアイコンが設定されていない場合は、Android では白色でレンダリングされたアプリケーション アイコンが表示されます。

onMessageReceived のオーバーライド

FirebaseMessagingService.onMessageReceived メソッドをオーバーライドすると、受信した RemoteMessage オブジェクトに基づいてアクションを実行し、メッセージ データを取得できます。

Kotlin+KTX

override fun onMessageReceived(remoteMessage: RemoteMessage) {
    // TODO(developer): Handle FCM messages here.
    // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
    Log.d(TAG, "From: ${remoteMessage.from}")

    // Check if message contains a data payload.
    if (remoteMessage.data.isNotEmpty()) {
        Log.d(TAG, "Message data payload: ${remoteMessage.data}")

        // Check if data needs to be processed by long running job
        if (needsToBeScheduled()) {
            // For long-running tasks (10 seconds or more) use WorkManager.
            scheduleJob()
        } else {
            // Handle message within 10 seconds
            handleNow()
        }
    }

    // Check if message contains a notification payload.
    remoteMessage.notification?.let {
        Log.d(TAG, "Message Notification Body: ${it.body}")
    }

    // Also if you intend on generating your own notifications as a result of a received FCM
    // message, here is where that should be initiated. See sendNotification method below.
}

Java

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    // TODO(developer): Handle FCM messages here.
    // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
    Log.d(TAG, "From: " + remoteMessage.getFrom());

    // Check if message contains a data payload.
    if (remoteMessage.getData().size() > 0) {
        Log.d(TAG, "Message data payload: " + remoteMessage.getData());

        if (/* Check if data needs to be processed by long running job */ true) {
            // For long-running tasks (10 seconds or more) use WorkManager.
            scheduleJob();
        } else {
            // Handle message within 10 seconds
            handleNow();
        }

    }

    // Check if message contains a notification payload.
    if (remoteMessage.getNotification() != null) {
        Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
    }

    // Also if you intend on generating your own notifications as a result of a received FCM
    // message, here is where that should be initiated. See sendNotification method below.
}

onDeletedMessages のオーバーライド

状況によっては、FCM からメッセージが配信されないことがあります。これは、特定のデバイスが接続されたときにそのデバイスにまだ配信されていないアプリのメッセージが多すぎる(100 件を超えている)場合や、デバイスが 1 か月以上 FCM に接続されていない場合に起こります。このような場合、FirebaseMessagingService.onDeletedMessages() へのコールバックを受け取ることがあります。アプリ インスタンスがこのコールバックを受け取った場合は、アプリサーバーとの完全な同期を実行する必要があります。過去 4 週間以内にそのデバイスのアプリにメッセージを送信していない場合、onDeletedMessages() は呼び出されません。

バックグラウンド アプリの通知メッセージの処理

アプリがバックグラウンドで動作しているとき、Android ではシステムトレイに通知メッセージが送られます。ユーザーが通知をタップすると、デフォルトでアプリ ランチャーが開きます。

通知とデータ ペイロードの両方を含むメッセージ(および Notifications コンソールから送信されたすべてのメッセージ)がここに含まれます。この場合、通知はデバイスのシステムトレイに配信され、データ ペイロードはランチャー アクティビティのインテントの追加部分に配信されます。

アプリへのメッセージ配信については、FCM レポート ダッシュボードをご覧ください。このダッシュボードには、Android アプリの「インプレッション」(ユーザーが表示した通知)のデータとともに、Apple と Android のデバイスで送信および開封されたメッセージの数が記録されています。

送信リクエストを作成する

トピックを作成した後、クライアント側でクライアント アプリ インスタンスをトピックにサブスクライブするか、またはサーバー API を使用することによって、トピックにメッセージを送信できます。FCM の送信リクエストを初めて作成する場合は、サーバー環境と FCM に関するガイドで、バックグラウンドと設定に関する重要な情報をご確認ください。

バックエンドの送信ロジック内で、次のように目的のトピック名を指定します。

Node.js

// The topic name can be optionally prefixed with "/topics/".
const topic = 'highScores';

const message = {
  data: {
    score: '850',
    time: '2:45'
  },
  topic: topic
};

// Send a message to devices subscribed to the provided topic.
getMessaging().send(message)
  .then((response) => {
    // Response is a message ID string.
    console.log('Successfully sent message:', response);
  })
  .catch((error) => {
    console.log('Error sending message:', error);
  });

Java

// The topic name can be optionally prefixed with "/topics/".
String topic = "highScores";

// See documentation on defining a message payload.
Message message = Message.builder()
    .putData("score", "850")
    .putData("time", "2:45")
    .setTopic(topic)
    .build();

// Send a message to the devices subscribed to the provided topic.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);

Python

# The topic name can be optionally prefixed with "/topics/".
topic = 'highScores'

# See documentation on defining a message payload.
message = messaging.Message(
    data={
        'score': '850',
        'time': '2:45',
    },
    topic=topic,
)

# Send a message to the devices subscribed to the provided topic.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)

Go

// The topic name can be optionally prefixed with "/topics/".
topic := "highScores"

// See documentation on defining a message payload.
message := &messaging.Message{
	Data: map[string]string{
		"score": "850",
		"time":  "2:45",
	},
	Topic: topic,
}

// Send a message to the devices subscribed to the provided topic.
response, err := client.Send(ctx, message)
if err != nil {
	log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)

C#

// The topic name can be optionally prefixed with "/topics/".
var topic = "highScores";

// See documentation on defining a message payload.
var message = new Message()
{
    Data = new Dictionary<string, string>()
    {
        { "score", "850" },
        { "time", "2:45" },
    },
    Topic = topic,
};

// Send a message to the devices subscribed to the provided topic.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);

REST

POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1

Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
  "message":{
    "topic" : "foo-bar",
    "notification" : {
      "body" : "This is a Firebase Cloud Messaging Topic Message!",
      "title" : "FCM Message"
      }
   }
}

cURL コマンド:

curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
  "message": {
    "topic" : "foo-bar",
    "notification": {
      "body": "This is a Firebase Cloud Messaging Topic Message!",
      "title": "FCM Message"
    }
  }
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1

複数のトピックにメッセージを送信するには、条件を指定します。条件は、ターゲット トピックを指定するブール式です。たとえば次の条件では、TopicA に加えて TopicBTopicC のどちらか一方にもサブスクライブされているデバイスにメッセージが送信されます。

"'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"

FCM はまず、かっこ内の条件を評価し、次に左から右に式を評価していきます。上記の式では、いずれか 1 つのトピックのみにサブスクライブしているユーザーにはメッセージは送られません。同様に、TopicA にサブスクライブしていないユーザーにもメッセージは送られません。メッセージが送られるのは、次の組み合わせにサブスクライブしている場合のみです。

  • TopicATopicB
  • TopicATopicC

条件式には最大 5 つのトピックを含めることができます。

条件に基づいてメッセージを送信するには:

Node.js

// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
const condition = '\'stock-GOOG\' in topics || \'industry-tech\' in topics';

// See documentation on defining a message payload.
const message = {
  notification: {
    title: '$FooCorp up 1.43% on the day',
    body: '$FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'
  },
  condition: condition
};

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
getMessaging().send(message)
  .then((response) => {
    // Response is a message ID string.
    console.log('Successfully sent message:', response);
  })
  .catch((error) => {
    console.log('Error sending message:', error);
  });

Java

// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
String condition = "'stock-GOOG' in topics || 'industry-tech' in topics";

// See documentation on defining a message payload.
Message message = Message.builder()
    .setNotification(Notification.builder()
        .setTitle("$GOOG up 1.43% on the day")
        .setBody("$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.")
        .build())
    .setCondition(condition)
    .build();

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);

Python

# Define a condition which will send to devices which are subscribed
# to either the Google stock or the tech industry topics.
condition = "'stock-GOOG' in topics || 'industry-tech' in topics"

# See documentation on defining a message payload.
message = messaging.Message(
    notification=messaging.Notification(
        title='$GOOG up 1.43% on the day',
        body='$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.',
    ),
    condition=condition,
)

# Send a message to devices subscribed to the combination of topics
# specified by the provided condition.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)

Go

// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
condition := "'stock-GOOG' in topics || 'industry-tech' in topics"

// See documentation on defining a message payload.
message := &messaging.Message{
	Data: map[string]string{
		"score": "850",
		"time":  "2:45",
	},
	Condition: condition,
}

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
response, err := client.Send(ctx, message)
if err != nil {
	log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)

C#

// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
var condition = "'stock-GOOG' in topics || 'industry-tech' in topics";

// See documentation on defining a message payload.
var message = new Message()
{
    Notification = new Notification()
    {
        Title = "$GOOG up 1.43% on the day",
        Body = "$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.",
    },
    Condition = condition,
};

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);

REST

POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1

Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
   "message":{
    "condition": "'dogs' in topics || 'cats' in topics",
    "notification" : {
      "body" : "This is a Firebase Cloud Messaging Topic Message!",
      "title" : "FCM Message",
    }
  }
}

cURL コマンド:

curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
  "notification": {
    "title": "FCM Message",
    "body": "This is a Firebase Cloud Messaging Topic Message!",
  },
  "condition": "'dogs' in topics || 'cats' in topics"
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1

次のステップ