开发者控制台

基于主题的消息传递

基于主题的消息传递

基于主题的消息传递(TBM)可让您向订阅相同主题的用户组发送消息。这是通过多播通信完成的。当应用安装在设备上时,该应用的实例具有名为设备注册ID的唯一标识符。基于主题的消息传递可以使用此ID传递到单个设备,让您可以在单个API调用中将消息发送到多个应用实例。您不是向单个设备注册ID发送消息,而是向主题发送消息。用户订阅了该主题,Amazon Device Messaging (ADM)将消息路由到与该主题相关的所有设备注册ID。消息类型包括数据消息、通知消息和带有数据的通知消息。

您可以使用主题接洽特定的客户群体。例如,如果您开发了一个天气预报应用程序,用户可以加入其所在地区的主题,接收天气通知。对于视频流媒体应用,用户可以订阅感兴趣的种类,以便在发布该种类的新电影或系列时获得自动更新。

基于主题的消息传递的考虑事项

以下是有关基于主题的消息传递的一些重要考虑事项:

  • 基于主题的消息传递以特定的安全配置文件为目标,这意味着主题可以向使用同一安全配置文件注册的多个应用的应用实例发送消息。
  • Amazon Device Messaging (ADM)在处理主题时会执行下列这些限制︰
    • 最多可以对一个安全配置文件分配100个主题。
    • 一个应用实例最多可订阅100个主题。
    • 一个主题最多可由10000个应用实例订阅。
  • 基于主题的消息传递需要更高的吞吐量,而不是更低的延迟。要向单个设备发送消息,应将消息目标设置为设备注册ID,而不是主题。
  • 基于主题的消息传递最适合公开可用的资源,如天气预报和股票报价。

为基于主题的消息传递注册安全配置文件

基于主题的消息传递可以让您执行以下操作:

  • 利用设备注册ID订阅主题
  • 让设备注册ID取消订阅主题
  • 将消息发送至主题

先决条件

要使用基于主题的消息传递,须满足以下先决条件。

  • 获取并存储了应用实例的注册ID。有关此过程的应用端的信息,请参阅集成您的应用
  • 交换了用于获取当前访问令牌的客户端凭证。有关详情,请参阅请求访问令牌

满足这些先决条件后,可以用编程方式注册与为TopicBasedMessaging生成的accessToken关联的安全配置文件。

请求格式

要向基于主题的消息传递注册安全配置文件,您的服务器组件(从现在起称为“您的服务器”)必须发出如下HTTP POST请求:

POST /v1/messaging/topic/registrations HTTP/1.1
Host: api.amazon.com
Authorization: Bearer (YOUR_ACCESS_TOKEN)
Content-Type: application/json
Accept: application/json

{
    "clientSecret":"YOUR_CLIENT_SECRET"
}

POST URL由第二行(Host)和第一行(POST)组成。当您将这些一起添加时,将获得完整的URL,如下所示:

https://api.amazon.com/v1/messaging/topic/registrations

请求要求

该请求本身由两部分组成:标头和消息正文。

标头字段

标头必须包含以下字段:

字段 描述 示例
Authorization ​在此包含您的当前访问令牌。值必须为: Bearer (YOUR_ACCESS_TOKEN) Authorization: Bearer <Your access Token>
Content-Type 值必须为:application/json Content-Type: application/json
Accept 值必须为:application/json Accept: application/json

消息正文参数

对于消息正文的内容,您需要提供具有字符串的JSONObject,该字符串包含以下参数。

参数 描述 示例
clientSecret 您的客户端凭证的“客户端密钥”部分。 clientSecret=<YOUR_CLIENT_SECRET>

响应格式

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

HTTP/1.1 200
X-Amzn-RequestId: <Amazon RequestId>
Content-Type:application/json
content-length:140

{
    "message": "安全配置文件amzn1.application.<32个十六进制字符>已向TopicBasedMessaging注册。"
}

如果客户端凭证的安全配置文件已向TopicBasedMessaging注册,则ADM会送回200状态代码。如果收到200状态代码,响应消息将在JSONObject中包含以下参数:

  • message: 包含与用于创建accessToken的凭证关联的安全配置文件,以及消息正文中使用的clientSecret。响应应该是现在已向TopicBasedMessaging注册的安全配置文件。

如果客户端凭证的安全配置文件未向TopicBasedMessaging注册,则ADM会送回错误代码。对于200以外的代码,响应消息可能在JSONObject正文中包含此参数:

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

如需了解更多错误状态代码,请参见下文。

ADM错误状态代码

代码 描述 示例
400 消息正文中的clientSecret与用于创建accessToken的clientSecret不同。发送者在消息正文中应使用与用于创建accessTokenclientSecret相同的clientSecret。 "reason": "所提供的clientSecret与安全配置文件amzn1.application.<32个十六进制字符>不关联"
401 所提供的访问令牌无效。发送者应刷新其访问令牌。有关刷新访问令牌的信息,请参阅请求访问令牌 "reason":"AccessTokenExpired"
429 请求者已超出允许的最大消息速率。发送者可以稍后根据响应中包含的Retry-After标头指示重试。为确保高可用性,ADM会限制在给定时间段内发送的消息数量。如果您有特定的容量要求,请联系我们并提供以下信息:
- 姓名
- 公司名称
- 电子邮件地址
- ​请求的TPS(每秒交易数)限制
- 理由
"reason":"MaxRateExceeded"
500 出现内部服务器错误。请求者可以稍后根据响应中包含的Retry-After标头重试。 不适用
503 服务器暂时不可用。请求者可以稍后根据响应中包含的Retry-After标头指示尝试。 不适用

响应标头字段

字段 描述 示例
X-Amzn-RequestId 由ADM创建的唯一标识请求的值。万一您在使用ADM时遇到问题,亚马逊可以使用此值来解决问题。 X-Amzn-RequestId: <Amazon RequestId>
Retry-After 对于429、500或503错误响应,将返回此字段。Retry-After指定预计服务不可用的时长。此值可以是一个从响应时算起的十进制数秒,也可以是HTTP格式的日期。有关此值可能的格式,请参阅HTTP/1.1规范第10.2.3节 Retry-After: 30
Content-Type 资源的内容类型:application/json Content-Type: application/json

创建TBM注册请求

以下是服务器软件如何请求向TopicBasedMessaging注册安全配置文件并处理ADM服务器响应的示例:

/**
 * 请求ADM向TopicBasedMessaging注册您的安全配置文件。
 */
public void tbmRegistration(String clientSecret, String accessToken) throws Exception
{
    // 消息的JSON负载表示。
    JSONObject payload = new JSONObject();

    //添加clientSecret值,该值用于创建标头中提供的accessToken。
    payload.put("clientSecret", clientSecret);


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

    //建立基本URL。
    String admUrlTemplate = "https://api.amazon.com/v1/messaging/topic/registrations";

    URL admUrl = new URL(admUrlTemplate);

    // 为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("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("错误: 向TBM注册安全配置文件的请求失败,并附带" +
                                         "%d响应代码和如下消息:%s"
                                         responseCode, errorContent));
    }
    else
    {
        // 请求成功。响应包含向TopicBasedMessaging注册的安全配置文件。
        String responseContent = parseResponse(conn.getInputStream());
        JSONObject parsedObject = new JSONObject(responseContent);

        String message = parsedObject.getString("message");
        
        //您可以从消息中提取安全配置文件,以验证其是否与用于
        //创建请求标头中使用的accessToken的安全配置文件相同。
        
    }

}

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();
}

通过客户端应用实例订阅主题,或取消订阅主题

客户端应用可以订阅任何现有主题或创建新主题。当客户端应用订阅关联安全配置文件中当前不存在的新主题时,会创建一个新主题,与该安全配置文件关联的任何应用都可以订阅该主题。

要订阅主题,应用使用主题名称在ADM上调用subscribeToTopic()。您的应用经由onSubscribe()onSubscribeError()得到通知。您的应用应覆盖在com.amazon.device.messaging.ADMMessageHandlerJobBasecom.amazon.device.messaging.ADMMessageHandlerBase类中定义的这些回调方法。

订阅示例

final ADM adm = new ADM(this);
// subscribeToTopic()是异步的;
//如果subscribeToTopic()成功,将经由onSubscribe()回调通知您的应用。
//如果subscribeToTopic()失败,则onSubscribeError()回调。
adm.subscribeToTopic("weather");

要取消订阅主题,可让应用使用主题名称调用ADM unsubscribeFromTopic()。您的应用将经由onUnsubscribe()onUnsubscribeError()得到通知。您的应用应覆盖在com.amazon.device.messaging.ADMMessageHandlerJobBasecom.amazon.device.messaging.ADMMessageHandlerBase类中定义的这些回调方法。

取消订阅示例

final ADM adm = new ADM(this);
//unsubscribeFromTopic()是异步的;
//如果unsubscribeFromTopic()成功,您的应用将经由onUnsubscribe()回调得到通知。
//如果unsubscribeFromTopic()失败,则onUnsubscribeError()回调。
adm.unsubscribeFromTopic("weather");

这些新的API可能在较旧的Fire OS设备上不可用,这可能会导致这些设备上的应用崩溃。要避免这个问题,在调用这些API时使用try/catch块,如下所示。

try {
    Log.d(TAG, "subscribeToTopic: 尝试订阅主题:" + topic);
    adm.subscribeToTopic(topic);
} catch (Error e) {
    Log.d(TAG, "subscribeToTopic: 发送了错误,错误消息为" + e.getMessage());
}

回调方法描述

回调方法 描述
onSubscribe() 在成功订阅主题时调用,附带参数主题(应用订阅的主题)和应用环境。
onSubscribeError() 当订阅请求失败时调用,附带参数主题(应用尝试订阅的主题)、错误ID和应用环境。
onUnsubscribe() 在成功取消订阅主题时调用,附带参数主题(应用取消订阅的主题)和应用环境。
onUnsubscribeError() 当取消订阅请求失败时调用,附带参数主题(应用尝试取消订阅的主题)、错误ID和应用环境。

错误ID和描述

错误ID 描述
INVALID_MANIFEST 清单未定义必需的权限/广播接收器意图筛选条件。检查集成步骤并更新清单文件。
INTERNAL_ERROR 类似于HTTP 500状态代码的内部未处理错误。无需任何操作。
INVALID_TOPIC 提供的主题无效。主题名称不应为空值,主题命名应遵循此模式[a-zA-Z0-9-_.~%]{1,100}
NOT_REGISTERED_WITH_TBM 应用没有为TBM注册。为基于主题的消息传递注册您的应用。
UNREGISTERED 应用未注册或注册ID无效。调用ADM startRegister()再次注册。
ALREADY_SUBSCRIBED 应用已订阅主题。请勿尝试再次订阅。
NOT_SUBSCRIBED 应用已取消订阅,或给定主题不存在。请勿尝试取消订阅此主题。
MAXIMUM_SUBSCRIPTION_EXCEEDED 超出最大订阅限制是指以下任一种情况:,
(i)您的安全配置文件被分配了100个主题
(ii)您的应用实例订阅了100个主题
(iii)主题由10000个应用实例/registrationId订阅。
TOO_MANY_REQUESTS 订阅/取消订阅请求过多。请在30秒后重试。
SERVICE_NOT_AVAILABLE 无法与ADM服务通信。极少发生的情况。

回调方法的示例代码

public class MyADMMessageHandler extends ADMMessageHandlerJobBase{
    @Override
    protected void onSubscribe(final Context context, final String topic) {
        //在这里写下成功订阅的逻辑,例如
        //通知客户
        //将详细信息发送至服务器
    }

    @Override
    protected void onSubscribeError(final Context context, final String topic, final String errorId) {
        //根据错误ID在此处编写订阅错误的逻辑
    }

    @Override
    protected void onUnsubscribe(final Context context, final String topic) {
        //在这里写下成功取消订阅的逻辑,例如
        //通知客户
        //将详细信息发送至服务器
    }

    @Override
    protected void onUnsubscribeError(final Context context, final String topic, final String errorId) {
        //根据错误ID在此处编写取消订阅错误的逻辑
    }

}

接收和处理主题消息

ADM将消息传递给主题的方式与将其传递给单个注册ID的方式相同。有关更多信息,请参阅ADM消息类型

将消息发送至主题

先决条件

要向主题发送消息,须满足以下先决条件。

满足这些先决条件后,可以通过编程方式使用ADM向主题发送消息。

请求格式

要向主题发送消息,服务器会发出如下HTTP POST请求:

POST /v1/messaging/topic/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"},
    "notification":{"title":"ADM消息通知","body":"我们为您提供了最新优惠!"},
    "consolidationKey":"一些键",
    "expiresAfter":86400,
    "priority": "high",
    "topic":"SomeTopic"
}

POST URL由第二行(Host)和第一行(POST)组成。当您将这些一起添加时,将获得完整的URL,如下所示:

https://api.amazon.com/v1/messaging/topic/messages

请求要求

该请求本身由两部分组成:标头和消息正文。

标头字段

标头必须包含以下字段:

字段 描述 示例
Authorization ​在此包含您的当前访问令牌。值必须为: Bearer (YOUR_ACCESS_TOKEN) Authorization: <您的访问令牌>
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":{}

如果在发送的消息中同时提供了数据和通知负载,则合并的总大小不能大于6KB。
"data":{ "from":"Sam", "message":"嘿Max.你好吗", "时间":"10/26/2012 09:10:00" }
notification 要随消息一起发送的负载通知。通知消息需要使用预定义的键值对。这里是ADM通知支持的键值对

如果在消息中提供了数据和通知负载,则合并的总大小不能大于6KB。
"notification":{"title":"ADM消息通知","body":"我们为您提供了最新优惠!"}
priority 可选值。所发送消息的优先级有两个值:normalhighNormal是消息的默认优先级。如果应用正在前台运行,则Normal优先级消息可立即传送。当设备处于低电耗模式时,传送可能会推迟到下一个维护窗口期。对于high优先级消息,即使在低电耗模式下,ADM也会尝试立即将消息传送到应用。应用在一天内可接收的high优先级消息数量受到应用待机存储桶的限制。默认值为normal "priority": "high"
consolidationKey 可选值。这是一个任意字符串,用于指示多条消息在逻辑上是相同的,并允许ADM丢弃之前入队的消息,以便放入此新消息。不保证之前入队的消息不会送达。整合键的长度不能超过64个字符。 "consolidationKey":"SyncNow"
expiresAfter 可选值。在设备脱机的情况下ADM应保留消息的秒数。在此时间过后,消息可能会被丢弃。​允许值的范围为1(1秒钟)到2678400(31天)(含首尾值)。默认值为604800(1周)。 "expiresAfter":86400
topic 强制值。应用实例订阅的以及要向其发送消息的主题。字符串值应满足正则表达式模式[a-zA-Z0-9-_.~%]{1,100} "topic":"SomeTopic"

响应格式

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

HTTP/1.1 200
X-Amzn-RequestId: <Amazon RequestId>
Content-Type: application/json
Content-Length: 308

{
    "messageId":"<消息ID>"
}

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

  • messageId: 与发送的消息以及向其发送消息的主题相关联的ID

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

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

ADM错误状态代码

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

代码 描述 示例
400 在以下情况下,请求会被拒绝,并返回400响应代码。

1.主题值不满足正则表达式模式[a-zA-Z0-9-_.~%]{1,100}

2.主题不存在有效的订阅,意味着没有registrationId订阅它。

3.主题之外的无效输入参数,例如:
- InvalidData - 未提供数据或通知字段。
- InvalidConsolidationKey - 键的长度大于64。
- InvalidExpiration - 值小于1或大于2678400。
- InvalidType - 标头中提供的typeVersionacceptType值并非允许的值

4.accessToken确定的安全配置文件并未向TopicBasedMessaging注册。
"reason": "安全配置文件amzn1.application.<32个十六进制字符>没有为TopicBasedMessaging注册。"
401 所提供的访问令牌无效。发送者应刷新其访问令牌。有关刷新访问令牌的信息,请参阅请求访问令牌 "reason":"AccessTokenExpired"
413 data参数中提供的消息负载已超出允许的最大数据大小(6KB)。 "reason":"MessageTooLarge"
429 请求者已超出允许的最大消息速率。发送者可以稍后根据响应中包含的Retry-After标头指示重试。为确保高可用性,ADM会限制在给定时间段内发送的消息数量。如果您有特定的容量要求,请联系我们并提供以下信息:
- 姓名
- 公司名称
- 电子邮件地址
- ​请求的TPS(每秒交易数)限制
- 理由
"reason":"MaxRateExceeded"
500 出现内部服务器错误。请求者可以稍后根据响应中包含的Retry-After标头指示重试。 不适用
503 服务器暂时不可用。请求者可以稍后根据响应中包含的Retry-After标头指示尝试。 不适用

响应标头字段

字段 描述 示例
X-Amzn-RequestId 由ADM创建的唯一标识请求的值。万一您在使用ADM时遇到问题,亚马逊可以使用此值来解决问题。 X-Amzn-RequestId: <Amazon RequestId>
Retry-After 对于429、500或503错误响应,将返回此字段。Retry-After消息指定预计服务不可用的时长。此值可以是一个从响应时算起的十进制数秒,也可以是HTTP格式的日期。有关此值可能的格式,请参阅HTTP/1.1规范第10.2.3节 Retry-After: 30
Content-Type 资源的内容类型:application/json Content-Type: application/json

向主题发送消息并处理响应

以下代码示例演示了服务器软件如何向主题发送消息,以及如何处理ADM服务器响应:

/**
 *请求ADM将您的消息传递给订阅了给定主题的应用的特定实例。
 */
public void sendMessageToTopic(String topic, 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通知支持的键值对可以从文档中找到
    // (https://developer.amazon.com/zh/docs/adm/message-types.html#notification).
    JSONObject notification = new JSONObject();
    notification.put("title", "ADM消息通知");
    notification.put("body", "我们为您提供了最新优惠!");
    payload.put("notification", notification);

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

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

    // 为消息添加优先级。优先级的值决定了当设备处于低电耗/空闲模式时,
    // 消息会不会传送。
    payload.put("priority", "high");
    
    //将主题添加到消息正文中,同时使用该主题将消息传递到应用实例。
    payload.put("topic", topic);


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

    //建立基本URL
    String admUrlTemplate = "https://api.amazon.com/v1/messaging/topic/messages";

    URL admUrl = new URL(admUrlTemplate);

    // 为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("错误: 发送消息至主题%s的请求失败,并附带" +
                                         "%d响应代码和如下消息:%s"
                                         topic, responseCode, errorContent));
    }
    else
    {
        // 请求成功。响应包含messageId,
        //其与发送的消息以及向其发送消息的主题相关联的ID。

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

        String messageId = parsedObject.getString("messageId");
        //如果消息未传递到任何应用实例,
        //则可以用ADM传达messageId以解决问题。
}

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();
}