概述
【仅用于技术交流,未经允许禁止转载】
通常APP-Server使用http协议,告诉APP需要的展示内容图片,文字。这是一种最为常见的协议了。另外一种客户端内协议,比如新闻APP,点击一个焦点新闻,APP跳到相应的频道或专题。不难发现:前者是Server->APP通信,主要是内容类型的,后者是APP<—>APP内通信,主要是命令、动作类型的。
如何更好的处理APP内通信,这里有必要先介绍一下iOS平台进程间通信。
进程间通信
以iOS平台为例,进程间通信IPC,可以通过自定义URL Schema,然后APP实现-(BOOL) application:(UIApplication *) openURL:(NSURL *) sourceApplication:(NSString *) annotation:(id) 方法,可以处理一些命令和参数。其中sourceApplication即调用者的bundleIdentifier。比如下面这条URL,会调起twitter客户端,并拉起发送推文的界面。
URL Scheme完美解决了同一个操作系统中,调用APP和传输消息和命令的问题。URL Schema机制,给开发者提供了很好的思路,可以解决APP内通信问题。CPMessage应运而生。
什么是CPMessage
CPMessage(Cross Platform Message跨平台消息)可以当做服务器/客户端(让客户端处理一些命令),或者客户端内部通信的桥梁。通信的发起者可能是服务器、Push、用户操作、HTML5或者是别的APP。客户端根据具体命令和相应参数,执行相应动作。总之,CPMessage思路源于iOS平台的URL Schema机制。
CPMessage的机制
为了确保多平台的一致性,使用协议名称CPM作为开头,只是传递协议的方式不同。在iOS上,向客户端通信的方式是scheme+protocol,例如:
cpm://message.cmd?messageID=MsgID&urls=EncodedURLStrings&uid=uid&ex1=ex1&ex2=ex2&ex3=ex3。
下面将各部分标注一下:
一般来说:命令名称都是message.cmd,经常改变的是messageID(下面详细介绍)。参数列表中,messageID表明是那种动作,比如,打开一个webview、打开新的页面、还是调起登录框等。参数列表后面是一些辅助参数。下面的表格给出了一些示例。
参数 | 用途 | 格式 | 示例 |
MessageID | 用来指定客户端做出的响应 | string | 1.1 |
url | 需跳转的网址 | string | www.baidu.com |
ex1 | 额外参数 | string | “03” |
另外,如果ex1、ex2和ex3不够用。还可以使用more参数。比如:more = {"title":"helloworld","name":"jack"};more参数格式为json格式。方便客户端解析字段。
MessageID
MessageID根据可分为mainID和subID,比如MessageID=2.1。那么mainID是2,subID是1。一般来说mainID是操作类型的区分,比如:
mainID=1表示打开页面。
mainID=2是传递数据。
mainID=3 是打开其他sdk等等。
subID则是一些操作的细分类型,比如:打开那个页面等等。
APP都是跨平台的,至少有iPhone,Android客户端,那么URL Schema头部将是不同的。举例:Server给的H页面中,某个链接是个CPM消息,URL Scheme配合CPM的使用,如下格式:
myapp://cpm://xxxxxx。操作系统级别会读取到“myapp://”从而拉起客户端,客户端内部读取到“cpm://”
CPMessage的使用
开发者如何应用CPMessage到自己的APP中呢?你需要做三件事:第一,提前注册一些UIViewController和方法,封装成一个个CPMessageItem。
第二,当CPMessage过来的时候,将其解析成CPMessageCommand,其中CPMessageCommand包含了参数。
第三,使用一个CPMManager类,将command 和 item对应到某一个UIViewController,并使用NSInvocation,调用之前注册好的方法。
那么,回到刚才一开始的问题,我们假设MessageID = 1.1 即跳转频道,ex1即频道的index。那么,Server将下面的URL消息,封装到焦点新闻里
cpm://message.cmd?messageID=1.1&ex1=03
客户端TabViewController注册一个调转频道的method -(void) switchToChannel:(int)index,执行CPMManager,TabViewController 执行switchToChannel方法。可以很优雅的解决新闻APP,调转到任意频道的问题。
CPMManager的UML图+流程图
CPMManger 的几个关键函数
prepareProcessor 事先注册好一些类和类方法
handleUrl:接收url,带有cpm schema的url
onCPMIsComing:参数是CPMCommand
handleCPM:参数CPMCommand。最终通过[NSInvocation invoke]实现回调。
CPMManger的实现
这里只列出部分代码,即CPMManger的关键代码:
//向CPMParse注册的回调函数。
- (void)onCPMIsComing:(NSNotification *)noti
{
NSDictionary *userInfo = noti.userInfo;
if ([userInfo isKindOfClass:[NSDictionary class]]) {
CPMCommand *command = [userInfo objectForKey:kCPMessageParserNotificaitonCommandKey];//
if ([command isKindOfClass:[CPMCommand class]]) {
[self handleCPM:command];
}
}
}
//真正处理CPMessage的地方。通过NSInvocation将message命令和UIViewController关联起来
- (void)handleCPM:(CPMCommand *)message
{
[self prepareProcessors];
CPMItem *item = (CPMItem *)[self.processors objectForKey:NSStringFromCPMessageCommand(message)];
if (nil == item) {
return;
}
[item getInfoFromCPMessageCommand:message];
UIViewController *visibleCtrl = [self.assit topViewController];
Class receiverClass = NSClassFromString(item.className);
if (item.classMethod != nil) {
[self performClassSelector:item.classMethod onTarget:receiverClass withArgs:item.classArgs];
[item clearInfoFromCPMessageCommand];
} else if (!item.isOpenAsNew && [visibleCtrl isMemberOfClass:receiverClass]) { // if target viewcontroller is top viewcontroller
if (item.reloadMethod != nil) {
[self performSelector:item.reloadMethod onTarget:visibleCtrl withArgs:item.initialArgs];
} else if (item.optMethod != nil) {
[self performSelector:item.optMethod onTarget:visibleCtrl withArgs:item.optArgs];
} else {
// no reloadSEL and no optSEL then do nothing
}
[item clearInfoFromCPMessageCommand];
} else {
UIViewController *baseCtl = [item.processor perpareOpeningWithHelpFrom:self.assit];
//必须要dispatch_async,否则不会回调viewWillDisappear和viewDidDisappear
dispatch_async(dispatch_get_main_queue(), ^{
// if target viewcontroller is top viewcontroller
if (!item.isOpenAsNew && [baseCtl isMemberOfClass:receiverClass]) {
if (item.reloadMethod != nil) {
[self performSelector:item.reloadMethod onTarget:baseCtl withArgs:item.initialArgs];
} else if (item.optMethod != nil) {
[self performSelector:item.optMethod onTarget:baseCtl withArgs:item.optArgs];
} else {
// no reloadSEL and no optSEL then do nothing
}
} else {
id receiverSuper = [receiverClass alloc];
id receiver = [self performSelector:item.initialMethod onTarget:receiverSuper withArgs:item.initialArgs];
if (nil == receiver || ![receiver isKindOfClass:[UIViewController class]]) {
[receiverSuper release];
} else {
[item.processor doOpen:receiver on:baseCtl];
[receiver release];
}
}
[item clearInfoFromCPMessageCommand];
});
}
}
/*
NSInvocation 施展才华的地方,selector和args 调用方法。
*/
- (id)performSelector:(SEL)aSelector onTarget:(id)target withArgs:(NSArray *)args
{
id ret = nil;
if (aSelector == nil || target == nil || ![target respondsToSelector:aSelector]) {
return ret;
}
NSMethodSignature *signature = [target methodSignatureForSelector:aSelector];
if (args.count + 2 != signature.numberOfArguments) {
return ret;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:aSelector];
[invocation setTarget:target];
for (int i = 2, j = 0; j < args.count; i++, j++) {
id arg = [args objectAtIndex:j];
[invocation setArgument:&arg atIndex:i];
}
[invocation invoke];
NSUInteger length = [[invocation methodSignature] methodReturnLength];
// void *buffer = (void *)malloc(length);
if (length > 0) {
[invocation getReturnValue:&ret];
}
return ret;
}
- (id)performClassSelector:(SEL)aSelector onTarget:(Class)target withArgs:(NSArray *)args
{
id ret = nil;
if (aSelector == nil || target == nil) {
return ret;
}
NSMutableString *argsInCTypes = [NSMutableString stringWithString:@"v"];
for (int i = 0; i < args.count; ++i) {
[argsInCTypes appendString:@"@:"];
}
NSMethodSignature *signature = [target methodSignatureForSelector:aSelector]; // [NSMethodSignature signatureWithObjCTypes:argsInCTypes.UTF8String];
if (signature == nil || args.count + 2 != signature.numberOfArguments) {
return ret;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:aSelector];
[invocation setTarget:target];
for (int i = 2, j = 0; j < args.count; i++, j++) {
id arg = [args objectAtIndex:j];
[invocation setArgument:&arg atIndex:i];
}
[invocation invoke];
NSUInteger length = [[invocation methodSignature] methodReturnLength];
// void *buffer = (void *)malloc(length);
if (length > 0) {
[invocation getReturnValue:&ret];
}
return ret;
}
//将UIViewController 的类名、selector分离出来,存到processor中。
- (void)prepareProcessors
{
NSMutableDictionary *processors = [NSMutableDictionary new];
CPMType type = -1;
CPMItem *item = nil;
//
type = CPMessageTypeSingleHTML5;
item = [CPMItem CPMessageItemWithProcessor:[CPMessageProcessors CPMessageProcessorForMessage:type]
className:@"WebViewController"
classMethod:nil
initMethod:@selector(initWithsURL:title:)
reloadMethod:@selector(loadWithsURL:title:)
optMethod:nil];
if (IS_VALID_CPMessageTYPE(type) && (nil != item)) {
[processors setObject:item forKey:CPMessageKeys[type]];
item = nil;
}
//添加其他item
self.processors = processors;
[processors release];
}
最后
以上就是机智嚓茶为你收集整理的【精】客户端(iOS、Android)/Server,APP内部的通信协议,跨平台方案的全部内容,希望文章能够帮你解决【精】客户端(iOS、Android)/Server,APP内部的通信协议,跨平台方案所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复