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

【仅用于技术交流,未经允许禁止转载】
        通常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.1
url需跳转的网址stringwww.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];
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值