js与nativede 通信

js与native通信的原理

但在切入正题前,需要先了解下iOS js与native通信的原理。了解这个原理,是理解PhoneGap代码的关键。

js –> native

在iOS中,js调用native并没有提供原生的实现,只能通过UIWebView相关的UIWebViewDelegate协议的

  1. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

方法来做拦截,并在这个方法中,根据url的协议或特征字符串来做调用方法或触发事件等工作,如

  1. /*
  2. * 方法的返回值是BOOL值。
  3. * 返回YES:表示让浏览器执行默认操作,比如某个a链接跳转
  4. * 返回NO:表示不执行浏览器的默认操作,这里因为通过url协议来判断js执行native的操作,肯定不是浏览器默认操作,故返回NO
  5. * /
  6. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
  7. NSURL *url = [request URL];
  8. if ([[url scheme] isEqualToString:@"callFunction") {
  9. //调用原生方法
  10. return NO;
  11. } else if (([[url scheme] isEqualToString:@"sendEvent") {
  12. //触发事件
  13. return NO;
  14. } else {
  15. return YES;
  16. }
  17. }

值得注意的是,通过这个方式,js调用native是异步的。

native –> js

native调用js非常简洁方便,只需要

  1. [webView stringByEvaluatingJavaScriptFromString:@"alert(‘hello world!‘)"];

并且该方法是同步的。

native调用js非常简单直接,所以PhoneGap解决的主要是js调用native的问题。

PhoneGap js –> native

我们通过一个js调用native的Dialog的例子做说明。

Dialog是一个PhoneGap的插件,可以看dialog 插件文档,学习下载并使用该插件。

  1. 这里有个很重要的事需要说明一下:
  2. 目前PhoneGap的文档更新非常不及时,特别是插件的使用方面,比如Dialog插件的使用,文档中写的是使用navigator.notification.alert,但是经过我的摸索,因为现在PhoneGap使用AMD的方式来管理插件,所以应该是使用cordova.require("cordova/plugin/notification").alert的方式来调用。
  3. 插件的合并方面,也有很多坑,主要是文档不全 - -|||

js部分

在html上添加一个button,然后通过下列代码调用:

  1. function alertDismissed() {
  2. // do something
  3. }
  4. function showAlert() {
  5. cordova.require("cordova/plugin/notification").alert(
  6. ‘You are the winner!‘,  // message
  7. alertDismissed,         // callback
  8. ‘Game Over‘,            // title
  9. ‘Done‘                  // buttonName
  10. );
  11. }

再看下对应的cordova/plugin/notification的代码:

  1. var exec = cordova.require(‘cordova/exec‘);
  2. var platform = cordova.require(‘cordova/platform‘);
  3. module.exports = {
  4. /**
  5. * Open a native alert dialog, with a customizable title and button text.
  6. *
  7. * @param {String} message              Message to print in the body of the alert
  8. * @param {Function} completeCallback   The callback that is called when user clicks on a button.
  9. * @param {String} title                Title of the alert dialog (default: Alert)
  10. * @param {String} buttonLabel          Label of the close button (default: OK)
  11. */
  12. alert: function(message, completeCallback, title, buttonLabel) {
  13. var _title = (title || "Alert");
  14. var _buttonLabel = (buttonLabel || "OK");
  15. exec(completeCallback, null, "Notification", "alert", [message, _title, _buttonLabel]);
  16. }
  17. }
  18. ....

可以看到alert最终其实是调用了exec方法来调用native代码的,exec方法非常关键,是PhoneGap js调用native的核心代码。

然后在源码中搜索exec对应的cordova/exec,查看exec方法的源码。

因为对应的cordova/exec源码非常长,我只能截取最关键的代码并做说明:

  1. define("cordova/exec", function(require, exports, module) {
  2. ...
  3. function iOSExec() {
  4. ...
  5. var successCallback, failCallback, service, action, actionArgs, splitCommand;
  6. var callbackId = null;
  7. ...
  8. // 格式化传入参数
  9. successCallback = arguments[0]; //成功的回调函数
  10. failCallback = arguments[1];    //失败的回调函数
  11. service = arguments[2];         //表示调用native类的类名
  12. action = arguments[3];          //表示调用native类的一个方法
  13. actionArgs = arguments[4];      //参数
  14. //默认callbackId为‘INVALID‘,表示不需要回调
  15. callbackId = ‘INVALID‘;
  16. ...
  17. //如果传入参数有successCallback或failCallback,说明需要回调,就设置callbackId,并存储对应的回调函数
  18. if (successCallback || failCallback) {
  19. callbackId = service + cordova.callbackId++;
  20. cordova.callbacks[callbackId] =
  21. {success:successCallback, fail:failCallback};
  22. }
  23. //格式化传入的service、action、actionArgs,并存储,准备native代码来调用
  24. actionArgs = massageArgsJsToNative(actionArgs);
  25. var command = [callbackId, service, action, actionArgs];
  26. commandQueue.push(JSON.stringify(command));
  27. ...
  28. //通过创建一个iframe并设置src,给native代码一个指令,开始执行js调用native的过程
  29. execIframe = execIframe || createExecIframe();
  30. if (!execIframe.contentWindow) {
  31. execIframe = createExecIframe();
  32. }
  33. execIframe.src = "gap://ready";
  34. ...
  35. }
  36. module.exports = iOSExec;
  37. });

为了调用native方法,exec方法做了大量初始化的工作,这么做的原因,还是因为iOS没有提供直接的方法来执行js调用native, 不能把参数直接传递给native,所以只能通过js端存储对应操作的所有参数,然后通过指令来让native代码来回调的方式间接完成。

native部分

之后,就走到了native代码的部分。

CDVViewController

前面js通过创建一个iframe并发送gap://ready这个指令来告诉native开始执行操作。native中对应的操作在 CDVViewController.m文件中的 webView:shouldStartLoadWithRequest:navigationType:方法:

  1. - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
  2. {
  3. NSURL* url = [request URL];
  4. /*
  5. * 判断url的协议以"gap"开头
  6. * 执行在js端调用cordova.exec()的command队列
  7. * 注:这里的command表示js调用native
  8. */
  9. if ([[url scheme] isElaqualToString:@"gap"]) {
  10. //_commandQueue即CDVCommandQueue类
  11. //从js端拉取command,即存储在js端commandQueue数组中的数据
  12. [_commandQueue fetchCommandsFromJs];
  13. //开始执行command
  14. [_commandQueue executePending];
  15. return NO;
  16. }
  17. ...
  18. }

到这里,其实已经走完js调用native的主要过程了。

之后,让我们再看下CDVCommandQueue中的fetchCommandsFromJs方法与executePending方法中做的事。

CDVCommandQueue

  1. - (void)fetchCommandsFromJs
  2. {
  3. // 获取js端存储的command,并在native暂存
  4. NSString* queuedCommandsJSON = [_viewController.webView stringByEvaluatingJavaScriptFromString:
  5. @"cordova.require(‘cordova/exec‘).nativeFetchMessages()"];
  6. [self enqueueCommandBatch:queuedCommandsJSON];
  7. }

fetchCommandsFromJs方法非常简单,不细说了。

executePending方法稍微复杂些,因为js是单线程的,而iOS是典型的多线程,所以executePending方法做的工作主要是让command一个一个执行,防止线程问题。

executePending方法其实与之后的execute方法紧密相连,这里一起列出,只保留关键代码:

  1. - (void)executePending
  2. {
  3. ...
  4. //_queue即command队列,依次执行
  5. while ([_queue count] > 0) {
  6. ...
  7. //取出从js中获取的command字符串,解析为native端的CDVInvokedUrlCommand类
  8. CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry];
  9. ...
  10. //执行command
  11. [self execute:command])
  12. ...
  13. }
  14. }
  15. - (BOOL)execute:(CDVInvokedUrlCommand*)command
  16. {
  17. ...
  18. BOOL retVal = YES;
  19. //获取plugin对应的实例
  20. CDVPlugin* obj = [_viewController.commandDelegate getCommandInstance:command.className];
  21. //调用plugin实例的方法名
  22. NSString* methodName = [NSString stringWithFormat:@"%@:", command.methodName];
  23. SEL normalSelector = NSSelectorFromString(methodName);
  24. if ([obj respondsToSelector:normalSelector]) {
  25. //消息发送,执行plugin实例对应的方法,并传递参数
  26. objc_msgSend(obj, normalSelector, command);
  27. } else {
  28. // There‘s no method to call, so throw an error.
  29. NSLog(@"ERROR: Method ‘%@‘ not defined in Plugin ‘%@‘", methodName, command.className);
  30. retVal = NO;
  31. }
  32. ...
  33. return retVal;
  34. }

可以看到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的代码:

  1. - (void)alert:(CDVInvokedUrlCommand*)command
  2. {
  3. NSString* callbackId = command.callbackId;
  4. NSString* message = [command argumentAtIndex:0];
  5. NSString* title = [command argumentAtIndex:1];
  6. NSString* buttons = [command argumentAtIndex:2];
  7. [self showDialogWithMessage:message title:title buttons:@[buttons] defaultText:nil callbackId:callbackId dialogType:DIALOG_TYPE_ALERT];
  8. }

前面用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的直接方法,做的处理也算合理到位。

特别是插件化的支持做的很好,但是文档着实不够给力。

时间: 2024-08-03 14:49:20

js与nativede 通信的相关文章

IOS中js与本地通信手记

如果只有一个webview要想js与ios的本地代码进行通信可以使用现成的phonegap框架,现因项目要求开发ipad版本的应用,页面中有多个需要多个webview所以需要自己编写js代码实现js与本地代码通信. 基本原理是:webview访问特殊的url前缀地址,然后在UIWebViewDelegate的shouldStartLoadWithRequest方法中对url进行过滤就可以达到js与本地代码进行通信了. 1.在MainViewController中实现UIWebViewDelega

Vue.js组件的通信之子组件向父组件的通信

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>componentChildToParentCommunication</title> <script src="js/vue.js"></script> </head> <template id

WebSocket+node.js创建即时通信的Web聊天服务器

1.使用nodejs-websocket nodejs-websocket是基于node.js编写的一个后端实现websocket协议的库, 连接:https://github.com/sitegui/nodejs-websocket. (1)安装 在项目目录下通过npm安装:npm install nodejs-websocket (2)创建服务器 //引入nodejs-websocket var ws = require("nodejs-websocket"); //调用creat

js面试题-----通信类

题目1:什么是同源策略及限制 题目2:前后端如何通信 Ajax   WebSocket   CORS 题目3:如何创建Ajax XMLHttpRequest对象的工作流程 兼容性处理 事件的触发条件 事件的触发顺序 util.json = function (options) { var opt = { url: '', type: 'get', data: {}, success: function () {}, error: function () {}, }; util.extend(op

js与java通信

js 调用java中的接口并传递参数给客户端处理方式: webView.addJavascriptInterface(new NewsDetail() , "newsDetail"); protected final class NewsDetail {    @JavascriptInterface  public void getContent(String imgUrl,String lineLink,String descContent,String shareTitle) {

WKWebView 里 JS 和 native 通信的例子

native 端 初始化 wkwebview,设置 message handler webView = WKWebView.init() let usecc = self.webView.configuration.userContentController usecc.add(self, name: "testecho") 实现 WKScriptMessageHandler协议 extension ViewController: WKScriptMessageHandler { fu

WebViewJavascriptBridge 进行js 与native通信。

1,  iOS端加载web页面.开启日志并给webView建立JS与OC的桥梁 - (void)viewWillAppear:(BOOL)animated { if (_bridge) { return; } // 1.加载网页 UIWebView* webView = [[UIWebView alloc] initWithFrame:self.view.bounds]; [self.view addSubview:webView]; // 2.开启日志 [WebViewJavascriptBr

【Node.js基础篇】(十)使用net模块和Readline模块实现Socket通信

Node.js的socket通信和C++.Java的非常相像,学过这两种语言的socket通信的同学可以很快就掌握好Node.js的socket通信.下面我们以实现一个Echo服务器的服务端和客户端为目的,学习一下Node.js的socket通信. 所谓的Echo服务器指的是这样一种服务器:客户端发送一条消息给服务端,服务端就把这条消息原封不动地返回给客户端. 服务端 服务端的实现分为三步: - 通过createServer创建一个server服务端 - 使用server的listen方法监听指

关于混合开发,oc与js互相通信的方法总结:

最近做公司的几个项目,主要以为h5为主,不能实现的功能用oc来写,这样就经常牵扯到oc调用js,或者js调用oc.先插嘴一句,对于目前而言,我对H5包装下的app的用户体验是极其的不满,真的没法和原生比较.对注重用户体验的公司还是比较倾向与混合开发. 根据个人长期摸索,和开发踩过的坑,在这稍微总结一下下..网上有的基本都很零散,对于开发在使用还需要具体根据项目情况来使用. oc与js互相调用目前我知道的时主要有4种直接的方式: 1. 苹果的javascriptcore.framework框架;