开发者控制台

发送消息

发送消息

您的服务器必须满足以下条件才能使用Amazon Device Messaging (ADM)将消息发送到应用实例:

  • 获取并存储了应用实例的注册ID。有关此过程的应用端的信息,请参阅集成您的应用

  • 交换了用于获取当前访问令牌的客户端凭证。有关详情,请参阅请求访问令牌

满足先决条件后,即可在开发者控制台中或通过编程方式使用ADM发送消息。

在开发者控制台中发送测试消息

可以在开发者控制台中设置ADM测试消息。

要设置测试通知,请执行以下步骤

  1. 登录开发者控制台。
  2. 在顶部导航栏中,选择应用与服务 > 设备消息
  3. 测试您的消息屏幕中,输入客户端ID和客户端密钥以及设备注册ID

    • 客户端ID和密钥 - 要查找您的客户端ID和客户端密钥,请参阅获取凭证
    • 设备注册ID – 要查找设备注册ID,请调用getRegistrationId
  4. 消息将在经过以下时长后过期部分中选择消息的持续时间。
  5. 选择消息类型。
  6. 要完成通知,请选择发送测试消息

检查您的设备是否收到了该消息。

消息负载和唯一性

使用ADM时,您可以发送负载大小可达6KB的消息。例如,即时消息程序可能会发送一条其负载类似如下的消息:

"data":
   {
   "from":"Sam",
   "message":"Hey, Max.How are you?",
   "time":"10/26/2012 09:10:00"
   }

另外,有些消息可能根本不包含任何负载数据。例如,一条简单的“同步”类型的状态消息,该消息旨在仅向应用或用户通知同步状况,而不传输唯一数据的负载。在此情况下,您可以传入一个空的data参数并包含consolidationKey参数的值。通过整合键,ADM可以标识具有冗余负载的同步消息,并尝试仅只将其中的一条消息传送到应用。

"data":{},
"consolidationKey":"Sync"

有时,即使携带负载的消息也不一定是唯一的。在此情况下,您也应尽可能使用consolidationKey参数来允许ADM整合消息。

"data":
   {
   "message":"Download \"Catcher in the Rye\"",
   "url":"https:\/\/s3.amazon.com\/CatcherInTheRye"
   },
"consolidationKey":"NewDownload"

请求格式

要发送消息,您的服务器组件(下文称为“您的服务器”)需要发出一个类似如下的HTTP POST请求:

POST /messaging/registrations/(REGISTRATION_ID_FOR_DESTINATION_APP_INSTANCE)/messages HTTP/1.1
Host: api.amazon.com
Authorization: Bearer (YOUR_ACCESS_TOKEN)
Content-Type: application/json
X-Amzn-Type-Version: com.amazon.device.messaging.ADMMessage@1.0
Accept: application/json
X-Amzn-Accept-Type: com.amazon.device.messaging.ADMSendResult@1.0

{
    "data":{"key1":"value1","key2":"value2"},
    "consolidationKey":"Some Key",
    "expiresAfter":86400
}

该POST请求的前两行一起构成了该请求的URL。该URL必须包含应接收消息的特定应用实例的注册ID。您之前应该已从该应用实例获取此注册ID。有关应用如何向您的服务器发送其注册ID的示例,请参阅ADM SDK下载包中包含的ADM示例应用。

https://api.amazon.com/messaging/registrations/(REGISTRATION_ID_FOR_DESTINATION_APP_INSTANCE)/messages

该请求本身由两部分组成:标头和消息正文。标头必须包含以下字段:

字段 描述 示例
Authorization ​在此包含您的当前访问令牌。值必须为: Bearer (YOUR_ACCESS_TOKEN) Authorization: Bearer Atc|<访问令牌>
Content-Type 值必须为:application/json Content-Type: application/json
X-Amzn-Type-Version 值必须为:com.amazon.device.messaging.ADMMessage@1.0 X-Amzn-Type-Version: com.amazon.device.messaging.ADMMessage@1.0
Accept 值必须为:application/json Accept: application/json
X-Amzn-Accept-Type 值必须为:com.amazon.device.messaging.ADMSendResult@1.0 X-Amzn-Accept-Type: com.amazon.device.messaging.ADMSendResult@1.0

对于消息正文的内容,您需要提供由字符串构成的JSONObject,其中包含以下参数。

参数 描述 示例
data 要随消息一起发送的负载数据。该数据必须采用JSON格式的键/值对形式,其中键和值必须为String值。该数据的总大小不能超过6KB,包括键和值以及引号(引起它们)、“:”字符(分隔它们)、逗号(分隔键/值对)和左右括号(括起字段)。键/值对之间的空格不计入负载大小。如果消息不包含负载数据,如在同步消息中,您可以传入一个空对象,例如"data":{} "data":{ "from":"Sam", "message":"Hey, Max.How are you?", "time":"10/26/2012 09:10:00" }
priority 可选值。所发送消息的优先级有两个值:普通优先级。普通是消息的默认优先级。如果应用正在前台运行,则普通优先级消息可立即传送。当设备处于低电耗模式时,传送可能会推迟到下一个维护窗口期。对于优先级消息,即使在低电耗模式下,ADM也会尝试立即将消息传送到应用。应用在一天内可接收的优先级消息数量受到应用待机存储桶的限制。默认值为普通 "priority": "high"
consolidationKey 可选值。这是一个任意字符串,用于指示多条消息在逻辑上是相同的,并允许ADM丢弃之前入队的消息,以便放入此新消息。请注意,不保证之前入队的消息不会送达。整合键的长度不能超过64个字符。 "consolidationKey":"SyncNow"
expiresAfter 可选值。在设备脱机的情况下ADM应保留消息的秒数。在此时间过后,消息可能会被丢弃。​允许值的范围为60(1分钟)到2678400(31天)(含60和2678400)。默认值为604800(1周)。

对于FireOS设备上的ADM,支持的最大值为31天。对于Windows设备上的ADM,支持的最大值为7天。
"expiresAfter":86400
md5 可选值。这是data参数的base-64编码MD5校验和。如果您为md5参数提供值,ADM会验证其准确性。如果您未提供值,服务器将代表您计算该值。无论哪种情况下,服务器都会将md5参数的值一直传递到设备,并在x-amzn-data-md5响应标头中返回计算的值。 请参阅“ADM Sample Code”文件夹中计算MD5校验和的示例。

为确保md5值与ADM服务器中的该值相匹配,您应为此参数计算一个值,如下所示:

  1. 使用基于UTF-8代码单元的键比较对消息的键/值对进行排序。
  2. 通过以下格式将一系列键/值对连接起来:"key1:value1,key2:value2"
  3. 删除键/值对之间的所有空格。“:”字符与任一键或值之间不得有空格。每个键/值对与“,”字符之间也不得有空格。
  4. 按照RFC 1321中定义的算法,使用所生成字符串的UTF-8字节计算SHA-256值。
  5. Base-64对该计算的128位输出进行编码。

响应格式

在成功接收并解释您的POST请求消息之后,ADM服务器会发送一条类似如下的HTTP响应消息:

HTTP/1.1 200
X-Amzn-Data-md5: <md5校验和>
x-amzn-Requestid: <十六进制ID>
Content-Type: application/json
X-Amzn-Type-Version: com.amazon.device.messaging.ADMSendResult@1.0
Content-Length: 308

{"registrationID":"amzn1.adm-registration.v1.<十六进制客户端凭证>"}

如果该消息被接受并入队以便传送到设备,ADM会返回200状态代码。对于200代码,响应消息在JSONObject中包含以下参数:

  • registrationID: 应用实例的当前注册ID。如果此值不同于您服务器传入的值,则您的服务器必须更新其记录才能使用此值。

如果该消息未被成功接受,ADM会返回错误(非200)状态代码。对于非200代码,响应消息可能在JSONObject的正文中包含以下参数:

  • reason: ​不接受请求的原因。

下表介绍了可能的ADM错误状态代码。

代码 描述 示例
400 输入的某些参数无效。可能的状态代码包括: InvalidRegistrationIdInvalidDataInvalidConsolidationKeyInvalidExpirationInvalidChecksumInvalidTypeUnregisteredUnregistered表示与注册ID关联的应用实例不再可用于接收消息。如果注册应用实例的设备的所有者发生变化,或者应用实例已请求不再接收消息,您可能会收到Unregistered状态代码。InvalidRegistrationId表示注册ID并不对应于由所提供的访问令牌标识的发送者。 "reason": "InvalidRegistrationId"
401 所提供的访问令牌无效。发送者应刷新其访问令牌。有关刷新访问令牌的信息,请参阅请求访问令牌 "reason":"AccessTokenExpired"
413 data参数中提供的消息负载已超出允许的最大数据大小(6KB)。 "reason":"MessageTooLarge"
429 请求者已超出允许的最大消息速率。发送者可能会按照响应中包含的Retry-After标头稍后重试。为确保高可用性,ADM会限制在给定时间段内可发送的消息数量。如果您有特定的容量要求,请联系我们,并在您的请求中提供以下信息:姓名、公司名称、电子邮件地址、请求的TPS(每秒事务数)限制、原因。 "reason":"MaxRateExceeded"
500 出现内部服务器错误。请求者必须按照响应中包含的Retry-After标头稍后重试。 不适用
503 服务器暂时不可用。请求者必须按照响应中包含的Retry-After标头稍后重试。 不适用

响应标头包含以下字段:

字段 描述 示例
X-Amzn-Data-md5 数据字段的已计算base-64编码MD5校验和。 X-Amzn-Data-md5: <md5校验和>
X-Amzn-RequestId 由ADM创建的唯一标识请求的值。万一您在使用ADM时遇到问题,亚马逊可以使用此值来解决问题。 X-Amzn-RequestId: <十六进制请求ID>
Retry-After 对于429、500或503错误响应,将返回此字段。Retry-After指定预计服务不可用的时长。此值可以是一个从响应时算起的整数秒(十进制),也可以是HTTP格式的日期。有关此值可能的格式,请参阅HTTP/1.1规范第14.37节 Retry-After: 120
Content-Type 资源的内容类型:application/json Content-Type: application/json
X-Amzn-Type-Version 描述响应的格式。 X-Amzn-Type-Version: com.amazon.device.messaging.ADMSendResult@1.0

发出消息请求并处理响应

以下代码示例举例说明了您的服务器软件如何发出发送消息的请求,然后处理ADM服务器的响应:

/**
 *请求ADM将消息传送到您的特定应用实例。
 */
public void sendMessageToDevice(String registrationId, String accessToken) throws Exception
{
    // 消息的JSON负载表示。
    JSONObject payload = new JSONObject();

    // 为您的消息内容定义键/值对,并将它们添加到
    // 消息负载。
    JSONObject data = new JSONObject();
    data.put("firstKey", "firstValue");
    data.put("secondKey", "secondValue");
    payload.put("data", data);

    // 添加一个整合键。如果多条消息正在等待传递给
    // 具有相同的整合键的特定应用实例,则ADM将会尝试传递
    // 最近添加的项目。
    payload.put("consolidationKey", "ADM_Enqueue_Sample");

    // 向消息添加值为1天的expires-after值。如果目标应用实例
    // 在expires-after时间期限内未联机,则不会传递消息。
    payload.put("expiresAfter", 86400);

    // 为消息添加优先级。优先级的值决定了当设备处于低电耗/空闲模式时,
    // 消息会不会传送。
    payload.put("priority", "high");


    // 将JSON对象中的消息转换为字符串。
    String payloadString = payload.toString();

    // 建立基本URL,其中包括要使用所需应用实例的
    // 注册ID替换的部分。因为我们使用String.format创建
    // URL,所以%1$s字符指定要替换的部分。
    String admUrlTemplate = "https://api.amazon.com/messaging/registrations/%1$s/messages";

    URL admUrl = new URL(String.format(admUrlTemplate,registrationId));

    // 为POST请求生成HTTPS连接。您无法通过HTTP建立
    // 连接。
    HttpsURLConnection conn = (HttpsURLConnection) admUrl.openConnection();
    conn.setRequestMethod("POST");
    conn.setDoOutput(true);

    // 设置内容类型和接受标头。
    conn.setRequestProperty("content-type", "application/json");
    conn.setRequestProperty("accept", "application/json");
    conn.setRequestProperty("X-Amzn-Type-Version", "com.amazon.device.messaging.ADMMessage@1.0");
    conn.setRequestProperty("X-Amzn-Accept-Type", "com.amazon.device.messaging.ADMSendResult@1.0");

    // 添加授权令牌作为标头。
    conn.setRequestProperty("Authorization", "Bearer " + accessToken);

    // 获取连接的输出流并写入消息负载。
    OutputStream os = conn.getOutputStream();
    os.write(payloadString.getBytes(), 0, payloadString.getBytes().length);
    os.flush();
    conn.connect();

    // 从连接获取响应代码。
    int responseCode = conn.getResponseCode();

    // 检查我们是否收到了失败响应,如果是,则获取失败的原因。
    if( responseCode != 200)
    {
        if( responseCode == 401 )
        {
            // 如果收到401响应代码,则表明访问令牌已过期。令牌应刷新
            // 并且可以重试此请求。
        }

        String errorContent = parseResponse(conn.getErrorStream());
        throw new RuntimeException(String.format("ERROR: The enqueue request failed with a " +
                                         "%d response code, with the following message: %s",
                                         responseCode, errorContent));
    }
    else
    {
        // 请求成功。响应包含您的应用的特定实例的规范注册ID,
        // 它与用于请求的ID可能不同。

        String responseContent = parseResponse(conn.getInputStream());
        JSONObject parsedObject = new JSONObject(responseContent);

        String canonicalRegistrationId = parsedObject.getString("registrationID");

        // 检查两个注册ID是否不同。
        if(!canonicalRegistrationId.equals(registrationId))
        {
            // 此时应使用此特定应用实例的正确注册ID来
            // 更新存储注册ID值的数据结构。
        }
    }

}

private String parseResponse(InputStream in) throws Exception
{
    // 从输入流中读取并转换为字符串。
    InputStreamReader inputStream = new InputStreamReader(in);
    BufferedReader buff = new BufferedReader(inputStream);

    StringBuilder sb = new StringBuilder();
    String line = buff.readLine();
    while(line != null)
   {
      sb.append(line);
      line = buff.readLine();
    }

    return sb.toString();
}

Last updated: 2024年12月4日