发送消息
您的服务器必须满足以下条件才能使用Amazon Device Messaging (ADM)将消息发送到应用实例:
满足先决条件后,即可在开发者控制台中或通过编程方式使用ADM发送消息。
在开发者控制台中发送测试消息
可以在开发者控制台中设置ADM测试消息。
要设置测试通知,请执行以下步骤
- 登录开发者控制台。
- 在顶部导航栏中,选择应用与服务 > 设备消息。
- 
    在测试您的消息屏幕中,输入客户端ID和客户端密钥以及设备注册ID。 
- 在消息将在经过以下时长后过期部分中选择消息的持续时间。
- 选择消息类型。
- 要完成通知,请选择发送测试消息。
检查您的设备是否收到了该消息。
消息负载和唯一性
使用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服务器中的该值相匹配,您应为此参数计算一个值,如下所示:
- 使用基于UTF-8代码单元的键比较对消息的键值对进行排序。
- 通过以下格式将一系列键/值对连接起来:"key1:value1,key2:value2"。
- 删除键/值对之间的所有空格。“:”字符与任一键或值之间不得有空格。每个键/值对与“,”字符之间也不得有空格。
- 按照RFC 1321中定义的算法,使用所生成字符串的UTF-8字节计算SHA-256值。
- 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 | 输入的某些参数无效。可能的状态代码包括: InvalidRegistrationId、InvalidData、InvalidConsolidationKey、InvalidExpiration、InvalidChecksum、InvalidType和Unregistered。Unregistered表示与注册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日

