标签:
js与native通信的原理
但在切入正题前,需要先了解下iOS js与native通信的原理。了解这个原理,是理解PhoneGap代码的关键。
js –> native
在iOS中,js调用native并没有提供原生的实现,只能通过UIWebView相关的UIWebViewDelegate协议的
- - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
方法来做拦截,并在这个方法中,根据url的协议或特征字符串来做调用方法或触发事件等工作,如
- /*
- * 方法的返回值是BOOL值。
- * 返回YES:表示让浏览器执行默认操作,比如某个a链接跳转
- * 返回NO:表示不执行浏览器的默认操作,这里因为通过url协议来判断js执行native的操作,肯定不是浏览器默认操作,故返回NO
- * /
- - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
- NSURL *url = [request URL];
- if ([[url scheme] isEqualToString:@"callFunction") {
-
-
- return NO;
- } else if (([[url scheme] isEqualToString:@"sendEvent") {
-
-
- return NO;
- } else {
- return YES;
- }
- }
值得注意的是,通过这个方式,js调用native是异步的。
native –> js
native调用js非常简洁方便,只需要
- [webView stringByEvaluatingJavaScriptFromString:@"alert(‘hello world!‘)"];
并且该方法是同步的。
native调用js非常简单直接,所以PhoneGap解决的主要是js调用native的问题。
PhoneGap js –> native
我们通过一个js调用native的Dialog的例子做说明。
- 这里有个很重要的事需要说明一下:
- 目前PhoneGap的文档更新非常不及时,特别是插件的使用方面,比如Dialog插件的使用,文档中写的是使用navigator.notification.alert,但是经过我的摸索,因为现在PhoneGap使用AMD的方式来管理插件,所以应该是使用cordova.require("cordova/plugin/notification").alert的方式来调用。
- 插件的合并方面,也有很多坑,主要是文档不全 - -|||
js部分
在html上添加一个button,然后通过下列代码调用:
- function alertDismissed() {
-
- }
-
- function showAlert() {
- cordova.require("cordova/plugin/notification").alert(
- ‘You are the winner!‘,
- alertDismissed,
- ‘Game Over‘,
- ‘Done‘
- );
- }
再看下对应的cordova/plugin/notification的代码:
- var exec = cordova.require(‘cordova/exec‘);
- var platform = cordova.require(‘cordova/platform‘);
-
- module.exports = {
-
-
- alert: function(message, completeCallback, title, buttonLabel) {
- var _title = (title || "Alert");
- var _buttonLabel = (buttonLabel || "OK");
- exec(completeCallback, null, "Notification", "alert", [message, _title, _buttonLabel]);
- }
- }
-
- ....
可以看到alert最终其实是调用了exec方法来调用native代码的,exec方法非常关键,是PhoneGap js调用native的核心代码。
然后在源码中搜索exec对应的cordova/exec,查看exec方法的源码。
因为对应的cordova/exec源码非常长,我只能截取最关键的代码并做说明:
- define("cordova/exec", function(require, exports, module) {
-
- ...
-
- function iOSExec() {
- ...
-
- var successCallback, failCallback, service, action, actionArgs, splitCommand;
- var callbackId = null;
-
- ...
-
-
- successCallback = arguments[0];
- failCallback = arguments[1];
- service = arguments[2];
- action = arguments[3];
- actionArgs = arguments[4];
-
-
- callbackId = ‘INVALID‘;
-
- ...
-
-
- if (successCallback || failCallback) {
- callbackId = service + cordova.callbackId++;
- cordova.callbacks[callbackId] =
- {success:successCallback, fail:failCallback};
- }
-
-
- actionArgs = massageArgsJsToNative(actionArgs);
-
- var command = [callbackId, service, action, actionArgs];
-
- commandQueue.push(JSON.stringify(command));
-
- ...
-
-
- execIframe = execIframe || createExecIframe();
- if (!execIframe.contentWindow) {
- execIframe = createExecIframe();
- }
- execIframe.src = "gap://ready";
-
- ...
- }
-
- module.exports = iOSExec;
-
- });
为了调用native方法,exec方法做了大量初始化的工作,这么做的原因,还是因为iOS没有提供直接的方法来执行js调用native, 不能把参数直接传递给native,所以只能通过js端存储对应操作的所有参数,然后通过指令来让native代码来回调的方式间接完成。
native部分
之后,就走到了native代码的部分。
CDVViewController
前面js通过创建一个iframe并发送gap://ready这个指令来告诉native开始执行操作。native中对应的操作在 CDVViewController.m文件中的 webView:shouldStartLoadWithRequest:navigationType:方法:
- - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
- {
- NSURL* url = [request URL];
-
-
- if ([[url scheme] isElaqualToString:@"gap"]) {
-
-
- [_commandQueue fetchCommandsFromJs];
-
- [_commandQueue executePending];
- return NO;
- }
- ...
- }
到这里,其实已经走完js调用native的主要过程了。
之后,让我们再看下CDVCommandQueue中的fetchCommandsFromJs方法与executePending方法中做的事。
CDVCommandQueue
- - (void)fetchCommandsFromJs
- {
-
- NSString* queuedCommandsJSON = [_viewController.webView stringByEvaluatingJavaScriptFromString:
- @"cordova.require(‘cordova/exec‘).nativeFetchMessages()"];
- [self enqueueCommandBatch:queuedCommandsJSON];
- }
fetchCommandsFromJs方法非常简单,不细说了。
executePending方法稍微复杂些,因为js是单线程的,而iOS是典型的多线程,所以executePending方法做的工作主要是让command一个一个执行,防止线程问题。
executePending方法其实与之后的execute方法紧密相连,这里一起列出,只保留关键代码:
- - (void)executePending
- {
- ...
-
- while ([_queue count] > 0) {
- ...
-
- CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry];
- ...
-
- [self execute:command])
- ...
- }
- }
-
- - (BOOL)execute:(CDVInvokedUrlCommand*)command
- {
- ...
- BOOL retVal = YES;
-
- CDVPlugin* obj = [_viewController.commandDelegate getCommandInstance:command.className];
-
- NSString* methodName = [NSString stringWithFormat:@"%@:", command.methodName];
- SEL normalSelector = NSSelectorFromString(methodName);
- if ([obj respondsToSelector:normalSelector]) {
-
- objc_msgSend(obj, normalSelector, command);
- } else {
-
- NSLog(@"ERROR: Method ‘%@‘ not defined in Plugin ‘%@‘", methodName, command.className);
- retVal = NO;
- }
- ...
- return retVal;
- }
可以看到js调用native plugin最终执行的是objc_msgSend(obj, normalSelector, command);这块代码,这里我们再拿js端的代码来进行理解。
之前js中的showAlert方法中我们书写了 exec(completeCallback, null, "Notification", "alert", [message, _title, _buttonLabel]);
故,这里的对应关系:
obj:“Notification”
normalSelector:“alert”
command:[message, title, buttonLabel]
CDVNotification
“Notification”真正对应的iOS类是CDVNotification。js端调用的插件名字”Notification”与真正的native类名并非完全对应,因为native因为平台的不同,有不同的命名规范。
看下CDVNotification的代码:
- - (void)alert:(CDVInvokedUrlCommand*)command
- {
- NSString* callbackId = command.callbackId;
- NSString* message = [command argumentAtIndex:0];
- NSString* title = [command argumentAtIndex:1];
- NSString* buttons = [command argumentAtIndex:2];
-
- [self showDialogWithMessage:message title:title buttons:@[buttons] defaultText:nil callbackId:callbackId dialogType:DIALOG_TYPE_ALERT];
- }
前面用objc_msgSend(obj, normalSelector, command);做消息发送,执行的便是这块代码,代码很好理解,就是对command再做解析,并显示。
最终效果:
点击”Done”,native会再回调执行js端的成功回调,这里对应的就是js里设置的alertDismissed方法。
到此为止,我们已经走完从js端调用native alert的全部过程了。
列下过程的核心代码:
js部分:cordova.js中的iOSExec()方法,指定js调用native的初始化工作,并发送开始执行的指令
native部分:CDVViewController:拦截js调用native的url协议,执行调用;CDVCommandQueue:执行js调用native的队列,调用对应的plugin
时序图
以上Dialog例子中,PhoneGap js调用native的时序图:
结语
PhoneGap还是很给力的,能做到主流平台全兼容着实不容易。
iOS端因为没有提供js调用native的直接方法,做的处理也算合理到位。
特别是插件化的支持做的很好,但是文档着实不够给力。
js与nativede 通信
标签:
原文地址:http://www.cnblogs.com/canghaixiaoyuer/p/4497241.html