【精】客户端(iOS、Android)/Server,APP内部的通信协议,跨平台方案

来源:互联网 发布:eviews面板数据f检验 编辑:程序博客网 时间:2024/05/21 06:56

【仅用于技术交流,未经允许禁止转载】
        通常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用来指定客户端做出的响应string1.1url需跳转的网址stringwww.baidu.comex1额外参数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];}



1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 眼睛被子弹打了怎么办 gta5买了2套衣服怎么办 gta5车被摧毁了怎么办 gta5车被损坏了怎么办 头盔玻璃磨花了怎么办 浇花喷水壶坏了怎么办 电力专用光缆撞了怎么办 国防电缆挖断了怎么办 国防光缆挖断了怎么办 房门前乱挂光纤线影响住户怎么办 挂断低于限高的光缆怎么办 开大车挂住光缆怎么办 风把树枝挂断压到车该怎么办 货车柴油冻住了怎么办 尖头鞋老是折尖怎么办 打 氟氯西林疼怎么办 多余的十字绣线怎么办 硅胶类的东西沾到蓝药水怎么办? ph计斜率不到90怎么办 ph计斜率低于90怎么办 顾客说衣服起球怎么办 买的手机壳太滑怎么办 硅胶手机壳太滑怎么办 磨砂手机壳太滑怎么办 被热胶棒烫了怎么办 车钢垫子次了怎么办 【图】机组主轴密封漏水怎么办? 孕妇吃了好多杏怎么办 怀孕6个月吃了好多杏怎么办 白色纯棉衣服染色了怎么办 红色硅胶壳黑了怎么办 小米6gps信号弱怎么办 网线头卡子断了怎么办 入户网线太短了怎么办 孩子弹钢琴大拇指出琴键怎么办 手指肿胀疼痛变粗怎么办 iphone系统占用内存大怎么办 手机系统占用内存大怎么办 头盔固定配件掉了怎么办 移动4g网络不好怎么办 wifi登录密码忘记了怎么办