分享

phonegap源码分析(三)------ IOS

ruanhero 发表于 2013-12-15 23:52:51 [显示全部楼层] 只看大图 回帖奖励 阅读模式 关闭右栏 0 5350
几个月前看过phonegap在Android和WP上的实现源码,当时苦于没mac环境,直到现在才抽出时间学习了一下phonegap是如何让JS与native串联起来的。
phonegap在IOS上和在WP很类似,由于IOS App内置的WebBrowser提供了比较好的与JS的互通机制,所以整个代码读起来比较轻松,架构图如下所示:
1343200617_2347.jpg
其中有这么几个点值得强调一下:

Phonegap IOS的项目结构及初始化过程 JS调用Native Native向JS返回结果

1)Phonegap IOS的项目结构及初始化过程
Phonegap IOS的项目结构非常简单,其实就是一个标准的单View的IOS App。只不过这个单view的视图文件xib是空的,它在MainViewController里添加了一个UIWebView,通过这个UIWebView来展现www里的html。
Phonegap的初始化过程包含JS端和Native端,这两端都是基于事件侦听的方式结合起来,JS端主要包含以下几个点:

onDOMContentLoaded:dom载入完成 onNativeReady:Native端WebUI载入完成 onCordovaReady:JS端相关objects都创建完成
deviceready:整个phonegap初始化完成

以上几个重要事件的先后顺序和hander侦听是通过channel组件架构,所谓的channel组件实际上就是phonegap自制的保障事件侦听和触发的组件,这块代码写得不错,短短2百多行代码,就打造了一个JS端事件侦听的框架,有兴趣的同学值得读一读。其中 onNativeReady是被native端调的,当native端WebUI初始化好后就会fire JS端onNativeReady事件,下面来看看native的几个关键的初始化节点:

AppDelegate.didFinishLaunchingWithOptions:App启动,初始化controller和view CDVViewController.viewDidLoad:view加载,初始化WebView CDVViewController.webviewDidFinishLoad:WebView加载,触发JS端onNativeReady

Native端存在着App->view->webview三个层次,以上三个点正好对应着这三个层次的加载。
2)JS调用Native
IOS的UIWebViewDelegate提供了shouldStartLoadWithRequest方法,它能截获web端url请求,因此phonegap就是通过在web端构造一个不可见的iframe,并置其src为gap://ready,Native端截获这个请求后就会得知此时JS端有请求。这块代码可见"cordova/exec"模块:
    createGapBridge = function() {
        gapBridge = document.createElement("iframe");
        gapBridge.setAttribute("style", "display:none;");
        gapBridge.setAttribute("height","0px");
        gapBridge.setAttribute("width","0px");
        gapBridge.setAttribute("frameborder","0");
        document.documentElement.appendChild(gapBridge);
    }那么具体的调用信息是如何传到native的呢吗?实际上是每次在js端调用exec时,phonegap会把调用信息放入cordova.commandQueue队列中,并通知native端。native端得到通知后,会调用js端"cordova/plugin/ios/nativecomm"模块里的代码拿到cordova.commandQueue队列中所有调用信息,并依次调用plugin来执行请求,源码如下所示:
JS端:
cordova.commandQueue.push(JSON.stringify(command));
if (cordova.commandQueue.length == 1 && !cordova.commandQueueFlushing) {
    if (!gapBridge) {
        createGapBridge();
    }
    gapBridge.src = "gap://ready";
}
Native端:shouldStartLoadWithRequestif ([[url scheme] isEqualToString:@"gap"]) {
    [self flushCommandQueue];
    return NO;
}
- (void) flushCommandQueue
{
    [self.webView stringByEvaluatingJavaScriptFromString:
         @"cordova.commandQueueFlushing = true"];
        
    // Keep executing the command queue until no commands get executed.
    // This ensures that commands that are queued while executing other
    // commands are executed as well.
    int numExecutedCommands = 0;
    do {
        numExecutedCommands = [self executeQueuedCommands];
    } while (numExecutedCommands != 0);
        
    [self.webView stringByEvaluatingJavaScriptFromString:
         @"cordova.commandQueueFlushing = false"];
}
- (int) executeQueuedCommands
{
    // Grab all the queued commands from the JS side.
    NSString* queuedCommandsJSON = [self.webView stringByEvaluatingJavaScriptFromString:
                                                                        @"cordova.require('cordova/plugin/ios/nativecomm')()"];
        
        
    // Parse the returned JSON array.
    NSArray* queuedCommands =
        [queuedCommandsJSON cdvjk_objectFromJSONString];
        
    // Iterate over and execute all of the commands.
    for (NSString* commandJson in queuedCommands) {
               
        if(![self.commandDelegate execute:
                 [CDVInvokedUrlCommand commandFromObject:
                  [commandJson cdvjk_mutableObjectFromJSONString]]])
                {
            static NSUInteger maxLogLength = 1024;
            NSString* commandString = ([commandJson length] > maxLogLength) ?
                [NSString stringWithFormat:@"%@[...]", [commandJson substringToIndex:maxLogLength]] :
                commandJson;
                        DLog(@"FAILED pluginJSON = %@", commandString);
                }
    }
        
    return [queuedCommands count];
}
这几段代码就算没学过Objective-C也应该能猜出个大概,这也算是一个供应者和消费者模式的应用实例。
3)Native向JS返回结果
上段代码也透露了Native调用JS的方式:
self.webView stringByEvaluatingJavaScriptFromString有了这个便利的方法,可以避免像在Android端使用ajax和polling这么复杂的实现。对于一个标准的phonegap的调用请求,native的plugin完成任务后,会统一调用JS端cordova.callbackSuccess和cordova.callbackError,见CDVPluginResult.m:-(NSString*) toSuccessCallbackString: (NSString*) callbackId
{
        NSString* successCB = [NSString stringWithFormat:@"cordova.callbackSuccess('%@',%@);", callbackId, [self toJSONString]];                        
        
        DLog(@"PluginResult toSuccessCallbackString: %@", successCB);
        return successCB;
}
-(NSString*) toErrorCallbackString: (NSString*) callbackId
{
        NSString* errorCB = [NSString stringWithFormat:@"cordova.callbackError('%@',%@);", callbackId, [self toJSONString]];
        
        DLog(@"PluginResult toErrorCallbackString: %@", errorCB);
        return errorCB;
}        
到此,Phonegap在IOS平台上的实现比较关键的几个点已分析完,后面我会基于之前对android和wp源码分析一起,来看看这样的实现方式会有那些限制和性能损耗

没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条