Vai alla console

在 Android 上向设备组发送消息

利用设备组消息传递功能,您可以向单个设备组添加多台设备。这类似于主题消息传递,但设备组消息传递加入了身份验证,以确保组成员资格仅受您的服务器管理。例如,如果要向不同的手机型号发送不同的消息,您的服务器可以向相应的设备组添加注册信息或从其中删除注册信息,并将相应的消息发送到每个设备组。设备组消息传递与主题消息传递的不同之处在于:设备组消息传递是从服务器上管理设备组,而不是直接在应用中管理。

您可以通过应用服务器上的旧版 XMPPHTTP 协议使用设备组消息传递功能。 基于旧版协议的 Node.js Firebase Admin SDK 也提供设备组消息传递功能。一个通知键最多支持 20 个成员设备。

管理设备组

向设备组发送消息之前,您必须:

  1. 为想要添加到该组的每一台设备获取注册令牌。

  2. 创建 notification_key。它用于将某个组(通常对应一名用户)映射到该组的所有关联注册令牌,以此来标识设备组。您可以在应用服务器或 Android 客户端应用上创建通知键。

设备组基本管理操作(创建和移除组,以及添加或移除设备)通常通过应用服务器执行。如需所支持的键的列表,请参阅旧版 HTTP 协议参考

Android 客户端应用也可以从客户端管理设备组。

在应用服务器上管理设备组

创建设备组

要创建设备组,需要发送一个 POST 请求并在其中提供组名称及组中所含设备的注册令牌列表。 FCM 会返回一个代表该设备组的新 notification_key

HTTP POST 请求

发送如下请求至 https://fcm.googleapis.com/fcm/notification

https://fcm.googleapis.com/fcm/notification
Content-Type:application/json
Authorization:key=API_KEY
project_id:SENDER_ID

{
   "operation": "create",
   "notification_key_name": "appUser-Chris",
   "registration_ids": ["4", "8", "15", "16", "23", "42"]
}

notification_key_name 是特定于设备组的唯一名称或标识符(例如用户名)。每组注册令牌的 notification_key_namenotification_key 都是唯一的。如果您的多个客户端应用的发送者 ID 都相同,则这些客户端应用的 notification_key_name 必须各不相同。这样可以确保消息只会发送到既定的目标应用。

响应格式

成功的请求将返回如下所示的 notification_key

{
   "notification_key": "APA91bGHXQBB...9QgnYOEURwm0I3lmyqzk2TXQ"
}

请保存此 notification_key 和对应的 notification_key_name,以便在后续操作中使用。

检索通知键

如果您需要检索现有的通知键,可在 GET 请求中使用 notification_key_name,如下所示:

https://fcm.googleapis.com/fcm/notification?notification_key_name=appUser-Chris
Content-Type:application/json
Authorization:key=API_KEY
project_id:SENDER_ID
{}

对于指定通知键名的每个 GET 请求,服务器都会返回一个独一无二的编码字符串。尽管每个字符串可能看起来像是不同的键,但它实际上是一个有效的“notification_key”值。

向设备组中添加设备及从中移除设备

要向现有设备组中添加设备或从中移除设备,需要发送一个 operation 参数设为 addremove 的 POST 请求,并提供要添加或移除的设备对应的注册令牌。

HTTP POST 请求

例如,要将注册 ID 为 51 的设备添加到 appUser-Chris,您应当发送一个如下所示的请求:

{
   "operation": "add",
   "notification_key_name": "appUser-Chris",
   "notification_key": "APA91bGHXQBB...9QgnYOEURwm0I3lmyqzk2TXQ",
   "registration_ids": ["51"]
}

响应格式

如果添加或移除设备的请求成功,将返回如下所示的 notification_key

{
   "notification_key": "APA91bGHXQBB...9QgnYOEURwm0I3lmyqzk2TXQ"
}

在 Android 客户端应用上管理设备组

当服务器不可用时,在客户端上管理设备组会非常有帮助。要在客户端创建设备组,设备上必须至少有一个 Google 帐号。请注意,在客户端创建通知键的流程与上面介绍的在服务器端创建的流程有很大不同。

在客户端创建设备组的操作步骤:

获取客户端 ID

  1. Google Developers Console 中打开您的项目。
  2. 从左上角的 菜单中,依次选择 API 和服务以及凭据
  3. 点击新建凭据,然后选择 OAuth 客户端 ID
  4. 创建客户端 ID 对话框中,选择网页应用作为应用类型,然后点击创建
  5. 复制显示的客户端 ID 值。该客户端 ID 代表您将用其生成 idToken 的 Google 帐号的“作用范围”。

在设备上验证 Google 帐号

从 Google Developers Console 获得客户端 ID 后,立即检查设备上是否存在 Google 帐号。

Java
Android

// This snippet takes the simple approach of using the first returned Google account,
// but you can pick any Google account on the device.
public String getAccount() {
    // This call requires the Android GET_ACCOUNTS permission
    Account[] accounts = AccountManager.get(this /* activity */).
            getAccountsByType("com.google");
    if (accounts.length == 0) {
        return null;
    }
    return accounts[0].name;
}

Kotlin
Android

// This snippet takes the simple approach of using the first returned Google account,
// but you can pick any Google account on the device.
@SuppressLint("MissingPermission")
fun getAccount(): String {
    // This call requires the Android GET_ACCOUNTS permission
    val accounts = AccountManager.get(this /* activity */).getAccountsByType("com.google")
    return if (accounts.isEmpty()) {
        ""
    } else accounts[0].name
}

获取身份验证令牌

接着,使用 GoogleAuthUtil 类获取一个身份验证令牌 (idToken)。 例如:

Java
Android

String accountName = getAccount();

// Initialize the scope using the client ID you got from the Console.
final String scope = "audience:server:client_id:"
        + "1262xxx48712-9qs6n32447mcj9dirtnkyrejt82saa52.apps.googleusercontent.com";

String idToken = null;
try {
    idToken = GoogleAuthUtil.getToken(this, accountName, scope);
} catch (Exception e) {
    Log.w(TAG, "Exception while getting idToken: " + e);
}

Kotlin
Android

val accountName = getAccount()

// Initialize the scope using the client ID you got from the Console.
val scope = "audience:server:client_id:" +
        "1262xxx48712-9qs6n32447mcj9dirtnkyrejt82saa52.apps.googleusercontent.com"

var idToken: String? = null
try {
    idToken = GoogleAuthUtil.getToken(this, accountName, scope)
} catch (e: Exception) {
    Log.w(TAG, "Exception while getting idToken: $e")
}

在设备组中添加或移除设备

构建一个发送至 https://fcm.googleapis.com/fcm/googlenotification 的 HTTP POST 请求,以便向设备组中添加注册令牌或从中移除注册令牌。请求标头需要将 project_id 设置为发送者 ID,并将 Content-Type 设置为 JSON。

添加到设备组

添加操作需要以下几个键: 设为 addoperation、设为在上述步骤中获取的 idTokenid_token,以及 notification_key_nameregistration_ids。下面显示的 userEmail 变量可以从 accounts[0].name 的值派生。客户端只有权管理映射至此电子邮件/帐号的设备组。

Java
Android

public String addToGroup(
        String senderId, String userEmail, String registrationId, String idToken)
        throws IOException, JSONException {
    URL url = new URL("https://fcm.googleapis.com/fcm/googlenotification");
    HttpURLConnection con = (HttpURLConnection) url.openConnection();
    con.setDoOutput(true);

    // HTTP request header
    con.setRequestProperty("project_id", senderId);
    con.setRequestProperty("Content-Type", "application/json");
    con.setRequestProperty("Accept", "application/json");
    con.setRequestMethod("POST");
    con.connect();

    // HTTP request
    JSONObject data = new JSONObject();
    data.put("operation", "add");
    data.put("notification_key_name", userEmail);
    data.put("registration_ids", new JSONArray(Arrays.asList(registrationId)));
    data.put("id_token", idToken);

    OutputStream os = con.getOutputStream();
    os.write(data.toString().getBytes("UTF-8"));
    os.close();

    // Read the response into a string
    InputStream is = con.getInputStream();
    String responseString = new Scanner(is, "UTF-8").useDelimiter("\\A").next();
    is.close();

    // Parse the JSON string and return the notification key
    JSONObject response = new JSONObject(responseString);
    return response.getString("notification_key");
}

Kotlin
Android

@Throws(IOException::class, JSONException::class)
fun addToGroup(
    senderId: String,
    userEmail: String,
    registrationId: String,
    idToken: String
): String {
    val url = URL("https://fcm.googleapis.com/fcm/googlenotification")
    val con = url.openConnection() as HttpURLConnection
    con.doOutput = true

    // HTTP request header
    con.setRequestProperty("project_id", senderId)
    con.setRequestProperty("Content-Type", "application/json")
    con.setRequestProperty("Accept", "application/json")
    con.requestMethod = "POST"
    con.connect()

    // HTTP request
    val data = JSONObject()
    data.put("operation", "add")
    data.put("notification_key_name", userEmail)
    data.put("registration_ids", JSONArray(arrayListOf(registrationId)))
    data.put("id_token", idToken)

    val os = con.outputStream
    os.write(data.toString().toByteArray(charset("UTF-8")))
    os.close()

    // Read the response into a string
    val `is` = con.inputStream
    val responseString = Scanner(`is`, "UTF-8").useDelimiter("\\A").next()
    `is`.close()

    // Parse the JSON string and return the notification key
    val response = JSONObject(responseString)
    return response.getString("notification_key")
}

如果操作成功,将返回 notification_key。 保存此 notification_key 和对应的 notification_key_name,以便在后续操作中使用。

从设备组中移除

移除操作需要以下几个键: 设为 removeoperation、设为在上述步骤中获取的 idTokenid_token,以及 notification_key_nameregistration_ids

Java
Android

// HTTP request
JSONObject data = new JSONObject();
data.put("operation", "remove");
data.put("notification_key_name", userEmail);
data.put("registration_ids", new JSONArray(Arrays.asList(registrationId)));
data.put("id_token", idToken);

Kotlin
Android

// HTTP request
val data = JSONObject()
data.put("operation", "remove")
data.put("notification_key_name", userEmail)
data.put("registration_ids", JSONArray(arrayListOf(registrationId)))
data.put("id_token", idToken)

向设备组发送下行消息

向设备组发送消息的过程与向单台设备发送消息十分类似。您需要将 to 参数设为特定于设备组的通知键。如需详细了解负载支持,请参阅消息类型。本页的示例展示了如何通过 HTTP 和 XMPP 协议向设备组发送数据消息。

设备组 HTTP POST 请求

https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA

{
  "to": "aUniqueKey",
  "data": {
    "hello": "This is a Firebase Cloud Messaging Device Group Message!",
   }
}

设备组 HTTP 响应

以下是一个“成功”的示例:notification_key 有 2 个关联的注册令牌,且消息已成功发送至这两个令牌:

{
  "success": 2,
  "failure": 0
}

以下是一个“部分成功”的示例:notification_key 有 3 个关联的注册令牌。消息仅成功发送至其中一个注册令牌。响应消息列出了未收到消息的注册令牌:

{
  "success":1,
  "failure":2,
  "failed_registration_ids":[
     "regId1",
     "regId2"
  ]
}

当消息未能发送至与某个 notification_key 关联的一个或多个注册令牌时,应用服务器隔一段时间会重试一次。

如果服务器尝试向没有成员的设备组发送消息,则响应如下所示(0 个成功,0 个失败):

{
  "success": 0,
  "failure": 0
}

设备组 XMPP 消息

<message id="">
  <gcm xmlns="google:mobile:data">
  {
      "to": "aUniqueKey",
      "message_id": "m-1366082849205" ,
      "data": {
          "hello":"This is a Firebase Cloud Messaging Device Group Message!"
      }
  }
  </gcm>
</message>

设备组 XMPP 响应

当消息成功发送至设备组中的任何一台设备时,XMPP 连接服务器都会发送 ACK 响应。当发送至组中所有设备的所有消息都失败时,XMPP 连接服务器会发送 NACK 响应。

以下是一个“成功”的示例:notification_key 有 3 个关联的注册令牌,且消息已成功发送至这 3 个令牌:

{
  "from": "aUniqueKey",
  "message_type": "ack",
  "success": 3,
  "failure": 0,
  "message_id": "m-1366082849205"
}

以下是一个“部分成功”的示例:notification_key 有 3 个关联的注册令牌。 消息仅成功发送至其中一个注册令牌。响应消息列出了未收到消息的注册令牌:

{
  "from": "aUniqueKey",
  "message_type": "ack",
  "success":1,
  "failure":2,
  "failed_registration_ids":[
     "regId1",
     "regId2"
  ]
}

当 FCM 连接服务器未能将消息发送至组中的所有设备时,应用服务器将收到 NACK 响应。

如需消息选项的完整列表,请参阅您所选连接服务器协议(HTTPXMPP)的参考信息。

向设备组发送上行消息

通过将消息定向至 to 字段中相应的通知键,客户端应用可以向设备组发送上行消息。

以下对 FCM 的调用将向相应通知键发送一条上行消息。该消息对象由以下键值对组成。

Java
Android

String to = "a_unique_key"; // the notification key
AtomicInteger msgId = new AtomicInteger();
FirebaseMessaging.getInstance().send(new RemoteMessage.Builder(to)
        .setMessageId(String.valueOf(msgId.get()))
        .addData("hello", "world")
        .build());

Kotlin
Android

val to = "a_unique_key" // the notification key
val msgId = AtomicInteger()
FirebaseMessaging.getInstance().send(RemoteMessage.Builder(to)
        .setMessageId(msgId.get().toString())
        .addData("hello", "world")
        .build())