zoukankan      html  css  js  c++  java
  • 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.  
    11.         return NO; 
    12.     } else if (([[url scheme] isEqualToString:@"sendEvent") { 
    13.         //触发事件 
    14.  
    15.         return NO; 
    16.     } else { 
    17.         return YES; 
    18.     } 
    值得注意的是,通过这个方式,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.     ); 
    再看下对应的cordova/plugin/notification的代码:
    1. var exec = cordova.require('cordova/exec'); 
    2. var platform = cordova.require('cordova/platform'); 
    3.  
    4. module.exports = { 
    5.  
    6.     /** 
    7.      * Open a native alert dialog, with a customizable title and button text. 
    8.      * 
    9.      * @param {String} message              Message to print in the body of the alert 
    10.      * @param {Function} completeCallback   The callback that is called when user clicks on a button. 
    11.      * @param {String} title                Title of the alert dialog (default: Alert) 
    12.      * @param {String} buttonLabel          Label of the close button (default: OK) 
    13.      */ 
    14.     alert: function(message, completeCallback, title, buttonLabel) { 
    15.         var _title = (title || "Alert"); 
    16.         var _buttonLabel = (buttonLabel || "OK"); 
    17.         exec(completeCallback, null, "Notification", "alert", [message, _title, _buttonLabel]); 
    18.     } 
    19.  
    20. .... 
    可以看到alert最终其实是调用了exec方法来调用native代码的,exec方法非常关键,是PhoneGap js调用native的核心代码。
     
    然后在源码中搜索exec对应的cordova/exec,查看exec方法的源码。
     
    因为对应的cordova/exec源码非常长,我只能截取最关键的代码并做说明:
    1. define("cordova/exec", function(require, exports, module) { 
    2.  
    3.     ... 
    4.  
    5.     function iOSExec() { 
    6.         ... 
    7.  
    8.         var successCallback, failCallback, service, action, actionArgs, splitCommand; 
    9.         var callbackId = null; 
    10.  
    11.         ... 
    12.  
    13.         // 格式化传入参数 
    14.         successCallback = arguments[0]; //成功的回调函数 
    15.         failCallback = arguments[1];    //失败的回调函数 
    16.         service = arguments[2];         //表示调用native类的类名 
    17.         action = arguments[3];          //表示调用native类的一个方法 
    18.         actionArgs = arguments[4];      //参数 
    19.  
    20.         //默认callbackId为'INVALID',表示不需要回调 
    21.         callbackId = 'INVALID'; 
    22.  
    23.         ... 
    24.  
    25.         //如果传入参数有successCallback或failCallback,说明需要回调,就设置callbackId,并存储对应的回调函数 
    26.         if (successCallback || failCallback) { 
    27.             callbackId = service + cordova.callbackId++; 
    28.             cordova.callbacks[callbackId] = 
    29.                 {success:successCallback, fail:failCallback}; 
    30.         } 
    31.  
    32.         //格式化传入的service、action、actionArgs,并存储,准备native代码来调用 
    33.         actionArgs = massageArgsJsToNative(actionArgs); 
    34.  
    35.         var command = [callbackId, service, action, actionArgs]; 
    36.  
    37.         commandQueue.push(JSON.stringify(command)); 
    38.  
    39.         ... 
    40.  
    41.         //通过创建一个iframe并设置src,给native代码一个指令,开始执行js调用native的过程 
    42.         execIframe = execIframe || createExecIframe(); 
    43.         if (!execIframe.contentWindow) { 
    44.             execIframe = createExecIframe(); 
    45.         } 
    46.         execIframe.src = "gap://ready"; 
    47.  
    48.         ... 
    49.     } 
    50.  
    51.     module.exports = iOSExec; 
    52.  
    53. }); 
    为了调用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.     NSURL* url = [request URL]; 
    3.  
    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. ... 
    到这里,其实已经走完js调用native的主要过程了。
     
    之后,让我们再看下CDVCommandQueue中的fetchCommandsFromJs方法与executePending方法中做的事。
     
    CDVCommandQueue
    1. - (void)fetchCommandsFromJs 
    2.     // 获取js端存储的command,并在native暂存 
    3.     NSString* queuedCommandsJSON = [_viewController.webView stringByEvaluatingJavaScriptFromString: 
    4.         @"cordova.require('cordova/exec').nativeFetchMessages()"]; 
    5.     [self enqueueCommandBatch:queuedCommandsJSON]; 
    fetchCommandsFromJs方法非常简单,不细说了。
     
    executePending方法稍微复杂些,因为js是单线程的,而iOS是典型的多线程,所以executePending方法做的工作主要是让command一个一个执行,防止线程问题。
     
    executePending方法其实与之后的execute方法紧密相连,这里一起列出,只保留关键代码:
    1. - (void)executePending 
    2.     ... 
    3.     //_queue即command队列,依次执行 
    4.     while ([_queue count] > 0) { 
    5.         ... 
    6.         //取出从js中获取的command字符串,解析为native端的CDVInvokedUrlCommand类 
    7.         CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry]; 
    8.         ... 
    9.         //执行command 
    10.         [self execute:command]) 
    11.         ... 
    12.     } 
    13.  
    14. - (BOOL)execute:(CDVInvokedUrlCommand*)command 
    15.     ... 
    16.     BOOL retVal = YES; 
    17.     //获取plugin对应的实例 
    18.     CDVPlugin* obj = [_viewController.commandDelegate getCommandInstance:command.className]; 
    19.     //调用plugin实例的方法名 
    20.     NSString* methodName = [NSString stringWithFormat:@"%@:", command.methodName]; 
    21.     SEL normalSelector = NSSelectorFromString(methodName); 
    22.     if ([obj respondsToSelector:normalSelector]) { 
    23.         //消息发送,执行plugin实例对应的方法,并传递参数 
    24.         objc_msgSend(obj, normalSelector, command); 
    25.     } else { 
    26.         // There's no method to call, so throw an error. 
    27.         NSLog(@"ERROR: Method '%@' not defined in Plugin '%@'", methodName, command.className); 
    28.         retVal = NO; 
    29.     } 
    30.     ... 
    31.     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的代码:
    1. - (void)alert:(CDVInvokedUrlCommand*)command 
    2.     NSString* callbackId = command.callbackId; 
    3.     NSString* message = [command argumentAtIndex:0]; 
    4.     NSString* title = [command argumentAtIndex:1]; 
    5.     NSString* buttons = [command argumentAtIndex:2]; 
    6.  
    7.     [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的直接方法,做的处理也算合理到位。
     
    特别是插件化的支持做的很好,但是文档着实不够给力。
  • 相关阅读:
    STRIDE威胁分析与DREAD威胁评价
    HashMap 几大问题
    java 集合中的错误检测机制
    科创人·StreamNative翟佳:开源模式价值为王,基础软件的未来在国内社区
    科创人·云柚智能CEO汤峥嵘:价值观一致奠定共事基础,技术创新加速行业变革
    科创人·微软中国CTO韦青:数智时代创业得跳下巨人肩膀,还需掌握基础知识和逻辑能力
    科创人研习社·微智云CEO 张虎:从CTO到创始人关键是扩大视野半径
    科创人·天云数据CEO雷涛:打造正确理解数智的认知体系
    neovim环境与vim简单使用
    MIT6.828——Lab3 PartA(麻省理工操作系统实验)
  • 原文地址:https://www.cnblogs.com/canghaixiaoyuer/p/4497241.html
Copyright © 2011-2022 走看看