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

幾個月前看過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端:shouldStartLoadWithRequest
if ([[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源碼分析一起,來看看這樣的實現方式會有那些限制和性能損耗

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章