使用场景:
在自有用户体系中,实现用户A给用户B发送消息,通知事务的处理结果。
举个例子:添加好友。
用户A发送好友请求给用户B,用户B收到请求后处理并将处理结果发送给用户A。
需求分析
- 选择一个消息推送平台:
国内推送平台:极光、个推等,国外有GCM(Google Cloud Message)。 - 在自己的服务器上开发一个API。
服务器接受用户A的请求消息,并通过推送平台将消息传递给用户B。 同理,用户B将处理结果发送给用户A。
通过上面的分析,我们结合服务器实现了功能需求。
但是,如果没有后台开发人员怎么办???
文章标题说的就是这个问题的解决方案。
AWS是亚马逊的云开发平台,类似国内的阿里云。SNS则是其中的一项服务,使应用程序、用户、终端设备能够通过云端即时发送或者接受消息。
我们不需要再开发一个API处理用户之间的消息传递,因为SNS已经全部做好了。我们需要做的便是选择一个推送平台、定义自己的消息格式,调用相关的SDK方法即可。
实现步骤(Android+GCM)
这里默认你已经有了AWS开发者账号。
1. 创建平台应用程序,得到ARN。
-
进入SNS控制面板(控制台首页搜索SNS即可找到),依次点击“应用程序”-->“创建平台应用程序”
- 应用程序名称:填写项目名称即可。
- 推送通知平台:根据自己的需求进行选择,这里我选择GCM,通过谷歌推送来实现。
- API密钥:在相应的推送平台注册应用后得到。
-
获取GCM平台下API密钥
a. 建议通过Firebase集成GCM,集成完好后,我们进入Google Cloud Platform,
b. 先选择通过Firebase创建的项目,然后在左侧菜单栏选择“API和服务”-->“凭据”。
c. 复制红框内名称为“Server Key”的密钥(Firebase默认创建的3个密钥,分别作用于Browser、Server、Android)。
因为我们想让AWS来处理消息传递,AWS属于Server一类。
将获得的密钥粘贴到“创建应用平台程序”窗口的“API密钥”一栏,点击右下角“创建平台应用程序”,稍后界面显示如下:
2. 将设备终端注册到ARN(代码实现),得到终端节点。
实现原理:集成GCM后,会有一个服务专门获取或更新token,我们只需要将这个token注册到ARN下即可。
//注册arn
private void sendRegistrationToServer(String token) {
// Implement this method to send token to your app server.
// 创建平台终端节点和管理设备令牌
client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
//retrieveEndpointArn方法是将保存在sp中的endpointArn取出,自己实现一下。
String endpointArn = retrieveEndpointArn();
boolean updateNeeded = false;
boolean createNeeded = ("".equals(endpointArn));
if (createNeeded) {
// No platform endpoint ARN is stored; need to call createEndpoint.
endpointArn = createEndpoint(token);
createNeeded = false;
}
System.out.println("Retrieving platform endpoint data...");
// Look up the platform endpoint and make sure the data in it is current, even if
// it was just created.
try {
GetEndpointAttributesRequest geaReq =
new GetEndpointAttributesRequest()
.withEndpointArn(endpointArn);
GetEndpointAttributesResult geaRes =
client.getEndpointAttributes(geaReq);
updateNeeded = !geaRes.getAttributes().get("Token").equals(token)
|| !geaRes.getAttributes().get("Enabled").equalsIgnoreCase("true");
} catch (NotFoundException nfe) {
// We had a stored ARN, but the platform endpoint associated with it
// disappeared. Recreate it.
createNeeded = true;
} catch (Exception ex) {
ex.printStackTrace();
}
if (createNeeded) {
createEndpoint(token);
}
System.out.println("updateNeeded = " + updateNeeded);
if (updateNeeded) {
// The platform endpoint is out of sync with the current data;
// update the token and enable it.
System.out.println("Updating platform endpoint " + endpointArn);
Map attribs = new HashMap();
attribs.put("Token", token);
attribs.put("Enabled", "true");
SetEndpointAttributesRequest saeReq =
new SetEndpointAttributesRequest()
.withEndpointArn(endpointArn)
.withAttributes(attribs);
client.setEndpointAttributes(saeReq);
}
}
/**
* @return never null
*/
private String createEndpoint(String token) {
String endpointArn = null;
try {
System.out.println("Creating platform endpoint with token " + token);
//创建endpointArn
String arn = "自己的arn";
CreatePlatformEndpointRequest cpeReq =
new CreatePlatformEndpointRequest()
.withPlatformApplicationArn(arn)
.withToken(token);
CreatePlatformEndpointResult cpeRes = client
.createPlatformEndpoint(cpeReq);
endpointArn = cpeRes.getEndpointArn();
//订阅主题
//String topic = "自己的主题";
// SubscribeRequest request = new SubscribeRequest()
// .withTopicArn(topic)
// .withProtocol("application")
// .withEndpoint(endpointArn);
// SubscribeResult subscribeResult = client.subscribe(request);
// Log.i(TAG, "subscribeResult: " + subscribeResult.getSubscriptionArn());
} catch (InvalidParameterException ipe) {
String message = ipe.getErrorMessage();
System.out.println("Exception message: " + message);
Pattern p = Pattern.compile(".*Endpoint (arn:aws:sns[^ ]+) already exists with the same token.*");
Matcher m = p.matcher(message);
if (m.matches()) {
// The platform endpoint already exists for this token, but with
// additional custom data that
// createEndpoint doesn't want to overwrite. Just use the
// existing platform endpoint.
endpointArn = m.group(1);
} else {
// Rethrow the exception, the input is actually bad.
throw ipe;
}
} catch (Exception ex) {
ex.printStackTrace();
}
//将endpointArn保存在sp中,自己实现一下。
storeEndpointArn(endpointArn);
return endpointArn;
}
完成以上步骤后,在程序启动后,便可以得到如下图所示内容。
- 令牌:GCM下发的token
- 终端节点 ARN:SNS为每一个终端生成的唯一标识。
3. 推送消息到某一个终端节点。
在SNS控制台中,选择一个终端节点,点击“发布到终端节点”,在弹出的界面中,输入消息,点击发送即可。
4. 终端间互发消息(代码实现)。
经过以上3个步骤,每个用户启动应用程序后,都会注册到SNS,并生成各自特有的终端节点。
那么,终端之间发送消息就简单了。
用户A向用户B发送消息,只需要知道用户B的终端节点号码即可。
/**
* 给指定终端发送消息
*
* @param endpointArn 对方的终端节点
* @param targetId 对方的id
* @param gcmMsg 消息体
* @return 发送成功后返回该条消息id
*/
public static Observable sendMsgToDevice(final String endpointArn, final String targetId, final GCMMsg gcmMsg) {
return Observable.fromCallable(new Callable() {
@Override
public String call() throws Exception {
String mId = SpUtil.getString(Constants.USER_ID);
//发消息
AmazonSNSClient client = new AmazonSNSClient(CognitoClientManager.getCredentials());
client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
PublishRequest request = new PublishRequest();
request.setTargetArn(endpointArn);
request.setMessageStructure("json");
String msg = "{\n" +
"\"GCM\": \"{ \\\"data\\\": { " +
"\\\"category\\\": \\\"" + gcmMsg.getCategory() + "\\\", " +
"\\\"title\\\": \\\"" + gcmMsg.getTitle() + "\\\", " +
"\\\"message\\\": \\\"" + gcmMsg.getMessage() + "\\\", " +
"\\\"url\\\": \\\"" + gcmMsg.getUrl() + "\\\", " +
"\\\"data\\\": \\\"" + gcmMsg.getData() + "\\\" } }\"\n" +
"}";
request.setMessage(msg);
PublishResult result = client.publish(request);
Log.i("SNS", "messageId: " + result.getMessageId());
return result.getMessageId();
}
});
}
至此,终端间互发消息的需求就完成了,全部借助AWS的SNS服务,无需另外开发后台API,自己只需要调用相关的SDK方法即可。