几个月前看过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"模块:
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源码分析一起,来看看这样的实现方式会有那些限制和性能损耗 |
|