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

来源:互联网 发布:网络平台建设 编辑:程序博客网 时间:2024/05/16 07:33

几个月前看过phonegap在Android和WP上的实现源码,当时苦于没mac环境,直到现在才抽出时间学习了一下phonegap是如何让JS与native串联起来的。

phonegap在IOS上和在WP很类似,由于IOS App内置的WebBrowser提供了比较好的与JS的互通机制,所以整个代码读起来比较轻松,架构图如下所示:

其中有这么几个点值得强调一下:

  • 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"模块:

[javascript] view plaincopy
  1.     createGapBridge = function() {  
  2.   
  3.         gapBridge = document.createElement("iframe");  
  4.         gapBridge.setAttribute("style""display:none;");  
  5.         gapBridge.setAttribute("height","0px");  
  6.         gapBridge.setAttribute("width","0px");  
  7.         gapBridge.setAttribute("frameborder","0");  
  8.         document.documentElement.appendChild(gapBridge);  
  9.     }  
那么具体的调用信息是如何传到native的呢?实际上是每次在js端调用exec时,phonegap会把调用信息放入cordova.commandQueue队列中,并通知native端。native端得到通知后,会调用js端"cordova/plugin/ios/nativecomm"模块里的代码拿到cordova.commandQueue队列中所有调用信息,并依次调用plugin来执行请求,源码如下所示:

JS端:

[javascript] view plaincopy
  1. cordova.commandQueue.push(JSON.stringify(command));  
  2. if (cordova.commandQueue.length == 1 && !cordova.commandQueueFlushing) {  
  3.     if (!gapBridge) {  
  4.         createGapBridge();  
  5.     }  
  6.     gapBridge.src = "gap://ready";  
  7. }  

Native端:shouldStartLoadWithRequest
[plain] view plaincopy
  1. if ([[url scheme] isEqualToString:@"gap"]) {  
  2.     [self flushCommandQueue];  
  3.     return NO;  
  4. }  

[plain] view plaincopy
  1. - (void) flushCommandQueue  
  2. {  
  3.     [self.webView stringByEvaluatingJavaScriptFromString:  
  4.      @"cordova.commandQueueFlushing = true"];  
  5.       
  6.     // Keep executing the command queue until no commands get executed.  
  7.     // This ensures that commands that are queued while executing other  
  8.     // commands are executed as well.  
  9.     int numExecutedCommands = 0;  
  10.     do {  
  11.         numExecutedCommands = [self executeQueuedCommands];  
  12.     } while (numExecutedCommands != 0);  
  13.       
  14.     [self.webView stringByEvaluatingJavaScriptFromString:  
  15.      @"cordova.commandQueueFlushing = false"];  
  16. }  

[plain] view plaincopy
  1. - (int) executeQueuedCommands  
  2. {  
  3.     // Grab all the queued commands from the JS side.  
  4.     NSString* queuedCommandsJSON = [self.webView stringByEvaluatingJavaScriptFromString:  
  5.                                     @"cordova.require('cordova/plugin/ios/nativecomm')()"];  
  6.       
  7.       
  8.     // Parse the returned JSON array.  
  9.     NSArray* queuedCommands =  
  10.     [queuedCommandsJSON cdvjk_objectFromJSONString];  
  11.       
  12.     // Iterate over and execute all of the commands.  
  13.     for (NSString* commandJson in queuedCommands) {  
  14.           
  15.         if(![self.commandDelegate execute:  
  16.          [CDVInvokedUrlCommand commandFromObject:  
  17.           [commandJson cdvjk_mutableObjectFromJSONString]]])  
  18.         {  
  19.             static NSUInteger maxLogLength = 1024;  
  20.             NSString* commandString = ([commandJson length] > maxLogLength) ?   
  21.                 [NSString stringWithFormat:@"%@[...]", [commandJson substringToIndex:maxLogLength]] :   
  22.                 commandJson;  
  23.             DLog(@"FAILED pluginJSON = %@", commandString);  
  24.         }  
  25.     }  
  26.       
  27.     return [queuedCommands count];  
  28. }  

这几段代码就算没学过Objective-C也应该能猜出个大概,这也算是一个供应者和消费者模式的应用实例。

3)Native向JS返回结果

上段代码也透露了Native调用JS的方式:

[plain] view plaincopy
  1. self.webView stringByEvaluatingJavaScriptFromString  
有了这个便利的方法,可以避免像在Android端使用ajax和polling这么复杂的实现。对于一个标准的phonegap的调用请求,native的plugin完成任务后,会统一调用JS端cordova.callbackSuccess和cordova.callbackError,见CDVPluginResult.m:
[plain] view plaincopy
  1. -(NSString*) toSuccessCallbackString: (NSString*) callbackId  
  2. {  
  3.     NSString* successCB = [NSString stringWithFormat:@"cordova.callbackSuccess('%@',%@);", callbackId, [self toJSONString]];              
  4.       
  5.     DLog(@"PluginResult toSuccessCallbackString: %@", successCB);  
  6.     return successCB;  
  7. }  
  8.   
  9. -(NSString*) toErrorCallbackString: (NSString*) callbackId  
  10. {  
  11.     NSString* errorCB = [NSString stringWithFormat:@"cordova.callbackError('%@',%@);", callbackId, [self toJSONString]];  
  12.       
  13.   
  14.     DLog(@"PluginResult toErrorCallbackString: %@", errorCB);  
  15.     return errorCB;  
  16. }     

到此,Phonegap在IOS平台上的实现比较关键的几个点已分析完,后面我会基于之前对android和wp源码分析一起,来看看这样的实现方式会有那些限制和性能损耗
原创粉丝点击