转载

JS 和 Native 的爱恨纠缠

JS 指的是 JavaScript,直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。与 Native 最大的区别是他不需要编译,在运行的过程中逐行进行解释。

Native 的 指的是 客户端 手机上 如 iOS 和 Android,这里就主要指 iOS 和 android ,Windows Phone 由于量级太小了就忽略。

正如标题所说的 “爱恨纠缠” 肯定没那么简单,

本文主要分为两个模块:

### : JS 和 Native 之间的交互 -> JsBridge 模块

当 JS 和 Native 之间的交互达到无缝对接的时候,是不是意味着 JS 可以 部分甚至全部替代了 Native 了呢?

### : JS 替代 Native 位置的趋势 -> 市面主流的 JS 跨平台技术介绍

在日常的开发工作中 JS 和 Native 的交互是不可或缺的,

存在的场景:

在客户端中,打开活动网页中要获取用户信息,好的用户体验肯定是直接就登录了客户端的信息,这里就需要 Native 客户端把用户信息通知给网页 JS 端。

相反的,当用户在网页中想调用系统的拍照等功能时,就需要本地 Native 客户端与 JS 的配合了。

目前的解决方案:

JS 调用 Android

  • (1) 允许 Android 执行 JS 脚本设置

Android(Native)与 JS(HTML)交互的接口函数是: mWebView.addJavascriptInterface(getHtmlObject(), “jsObj”); // jsObj 为桥连对象

Android 容许执行 JS 脚本需要设置: webSettings.setJavaScriptEnabled(true);

  • (2) JS(HTML)访问 Android(Native)代码

JS(HTML)访问 Android(Native)端代码是通过 jsObj 对象实现的,调用 jsObj 对象中的函数,如: window.jsObj.HtmlcallJava()

Android 调用 JS

Android(Java)访问js(HTML)端代码是通过loadUrl函数实现的,访问格式如:mWebView.loadUrl(“javascript: showFromHtml()”);

###上述方法存在的缺点:

  • 1.安全问题

但是因为安全问题,在 Android4.2 中(如果应用的 android:targetSdkVersion 数值为17+) JS 只能访问带有 @JavascriptInterface 注解的 Java 函数。因此如果你的开发版本比较高,需要在被调用的函数前加上 @JavascriptInterface 注解。

扩展一下 :这个漏洞有什么风险?

JS 中可以遍历 window 对象,找到存在 “getClass” 方法的对象的对象,然后再通过反射的机制,得到 Runtime 对象,然后调用静态方法来执行一些命令,比如访问文件的命令.

再从执行命令后返回的输入流中得到字符串,就可以得到文件名的信息了。然后想干什么就干什么,包括获取用户 SD 卡中的文件等等。

JS 和 Native 的爱恨纠缠

  • 2.调用流程较为复杂

由于目前的调用的方法都是单向无回调的调用,举个例子,如果 JS 网页端想登陆,调用 Native 客户端的一个登陆的接口,Native 登陆成功之后,调用 JS 网页的方法通知登陆成功,JS 在调用客户端的方法来获取 token。这样一个流程 Native 和 JS 两边个需要写两个接口,一个流程的代码也会分散开来,增加了代码的复杂度。

###iOS 与 JS 交互

Objective-C 语言调用 JavaScript 语言,是通过 UIWebView的 – (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script; 的方法来实现的。该方法向 UIWebView 传递一段需要执行的 JavaScript 代码最后获取执行结果。

JavaScript 语言调用 Objective-C 语言,并没有现成的 API,但是有些方法可以达到相应的效果。具体是利用 UIWebView 的特性:在 UIWebView 的内发起的所有网络请求,都可以通过 delegate 函数得到通知。

Objective-C 与 JS 的交互实现方式其实有很多,除了上面所述的 WebViewJavaScriptBridge,在IOS7之后,苹果将 JavaScriptCore 框架开放。由于 JavaScriptCore 不能做到跨平台的通用性,这里不做详细的介绍啦。

WebViewJavascriptBridge 是一套较为成熟的框架, 国内包括微信等 app 都在使用的 JS 交互模式。

使用的项目:

JS 和 Native 的爱恨纠缠

WebViewJavascriptBridge 的核心是在是在网页跳转的时候进行拦截。

当 Web 端加载一个链接,我们就在这个方法里面进行拦截,检查是否符合我们约定的规则,符合的话就拒绝加载并按这个链接携带的信息来执行相应的代码,不符合约定的规则就正常加载链接。

一般来说正常的请求都是 http 协议或者 https 协议,所以我们通常定义一个其他的协议以方便区分,比如 “jsb”。我们来约定一个规则:需要 Native 做的事情,就用 “action” 作为参数名,各 Action 对应的其他参数分别约定。我们举两个例子。

代表是触发获取消息队列的

egamescheme://__EGAME_QUEUE_MESSAGE__

代表是支付的行为 后面是相应的参数

egamescheme://action=pay&id=1000000&price=1

最终的实现的效果图如下:

在一端注册 Handler ,另一端调用对应的 Handler,就能实现带回调的交互

JS 和 Native 的爱恨纠缠

JS 和 Native 的爱恨纠缠

主要的流程图如下:

JS 和 Native 的爱恨纠缠

两端的实现原理是相似的,这里以 JS 调用 OC 方法为例(由 OC 注册,JS 主动触发(调用 callhandler 方法)):

  • 1.执行前提条件
  • 2.OC 在注册 (registerHandler) 的时候,OC 会注册一个回调函数

[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback)

该回调函数包括一个注册事件和一个回调方法,JSB 中的 base 实例会把该注册事件放进到 base 的一个消息池子(负责接受多个 OC 注册事件)中,方便后续处理

  • 3.网页加载出来后整个 OC 的执行流程

相当于 JS 的注册绑定方法—>当 UIWebview 把该网页加载出来的时候,在该 html 文件中会通过 JS 的 iframe 元素执行 JS 和 OC 端的绑定。

该方法是由于 webview 的 delegate 设置为了 JSB,所以会在 JSB 中触发其代理方法:shouldStartLoadWithRequest,然后在该方法中进行如下操作:

根据 url (此时的 url 是由上述的 iframe 元素带进来的,类似 :egamescheme://BRIDGE_LOADED ) ,来决定此时是走 registerHandler 还是 callHandler 方法,此时是 register 方法,那么则执行 JSBridgeBase 的 injectJavascriptFile 方法(正如函数名一样,此时执行在工程里面放置的JS文件(在我们的工程里面由于要兼容 JSBridge 的历史版本,所以这里是在 webViewDidFinishLoad 里面直接调用 injectJavascriptFile 方法)),这个文件的主要作用就是向 OC 发消息来定义一些常量,注册一些实例,定义一些回调方法,方便在 OC 的逻辑中进行处理和跟踪,文件执行完毕后,接下来就会查找在 base 中时候有 OC 要发送给 JS 的回调信息,没有则执行结束(这里显然没有)

  • 4.OC 和 JS 相互绑定过后,此时由 JS 触发 OC 函数

点击JS上的button会触发callHandler,会触发代理 shouldStartLoadWithRequest:

此时由于经由上述的JS文件执行的时候会执行一个叫做 window.WebViewJavascriptBridge 的注册

window.WebViewJavascriptBridge = {
        registerHandler: registerHandler,
        callHandler: callHandler,
        disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
        _fetchQueue: _fetchQueue,
        _handleMessageFromObjC: _handleMessageFromObjC
    };

由于js主动触发了 callHandler,url 所以变成了

*egamescheme:// EGAME_QUEUE_MESSAGE *,所以此时会走一个执行js函数的方法(猜测是通过上述js文件的作用从而能够解析出JS传递过来的信息)来从网页上获得网页的传递过来的信息!

{
  "callbackId" : "cb_2_1468844907930",
  "handlerName" : "testObjcCallback",
  "data" : {
    "foo" : "bar"
  }
}

然后进入 base 的 flushMessageQueue:函数,在里面通过解析该dict,从而获得注册事件名handlerName,然后获得该事件的回调,并调用那个事件的回调block

handler(message[@"data"], responseCallback);

至此,整个JS回调OC过程完成,达到了JS给OC发送数据的目的,同时如果OC想要给JS发送消息,只需把信息放进responseCallback就会回调给JS(方法就是第二部分的内容),以达到互相通信的目的

##市面主流的 JS 跨平台技术介绍

目前市面上主流的 JS 跨平台技术,有 ReactNative ,微信小程序,Weex 等。他们都有类似的宣传语, Learn once, write anywhere,Write Once Run Everywhere ,倡导的都是一套代码解决多平台的问题。我这边只做一个原理性的简单介绍,各位感兴趣的话可以去相应的官网查阅更多的资料。

  • 1.ReactNative 【React】

JS 和 Native 的爱恨纠缠

React.JS 是一个前端框架,在浏览器内 H5 开发使用,他在渲染 render() 这个环节,在经过各种布局算法之后,要在确定的位置去绘制这个界面元素的时候,需要通过浏览器去实现。他在响应触摸 touchEvent() 这个环节,依然是需要浏览器去捕获用户的触摸行为,然后回调 React.JS。

上面提到的都是纯网页,纯 H5,但如果我们把 render() 这个事情拦截下来,不走浏览器,而是走 Native 会怎样呢?

当 React.JS 已经计算完每个页面元素的位置大小,本来要传给浏览器,让浏览器进行渲染,这时候我们不传给浏览器了,而是通过一个 JS/OC 的桥梁,去通过 [[UIView alloc]initWithFrame:frame] 的 OC 代码,把这个界面元素渲染了,那我们就相当于用 React.JS 绘制出了一个 Native 的 View。

拿我们刚刚绘制出得 Native 的 View,当他发生 Native 源生的 – (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 触摸事件的时候,通过一个 OC/JS 的桥梁,去调用 React.JS 里面写好的点击事件 JS 代码。

这样 React.JS 还是那个 React.JS ,他的使用方法没发生变化,但是却获得了纯源生 Native 的体验,Native 的组件渲染,Native 的触摸响应

于是,这个东西就叫做 React-Native。

其实 ReactNative 最核心的功能其实是个桥梁,他的某些部分和我们上面讲的 bridge 很相似,但是他们实现的更为复杂和全面。

JS => OC

JS => JAVA

-• 链接了哪个模块,哪个模块就能用JS来操作,就能动态更新

-• 发现现有RN框架有些功能做不到了?扩展写个na代码模块,接入这个桥梁

React Native用iOS自带的JavaScriptCore作为JS的解析引擎,但并没有用到JavaScriptCore提供的一些可以让JS与OC互调的特性,而是自己实现了一套机制,这套机制可以通用于所有JS引擎上,在没有JavaScriptCore的情况下也可以用webview代替,实际上项目里就已经有了用webview作为解析引擎的实现,应该是用于兼容iOS7以下没有JavascriptCore的版本。

  • 2.微信小程序 【WA】

微信小程序使用了前端技术栈 JavaScript/WXML/WXSS。但和常规的前端开发又有一些区别:

JavaScript: 微信小程序的 JavaScript 运行环境即不是 Browser 也不是 Node.js。它运行在微信 App 的上下文中,不能操作 Browser context 下的 DOM,也不能通过 Node.js 相关接口访问操作系统 API。所以,严格意义来讲,微信小程序并不是 Html5,虽然开发过程和用到的技术栈和 Html5 是相通的。

WXML: 作为微信小程序的展示层,并不是使用 Html,而是自己发明的基于 XML 语法的描述。

WXSS: 用来修饰展示层的样式。官方的描述是 “ WXSS (WeiXin Style Sheets) 是一套样式语言,用于描述 WXML 的组件样式。WXSS 用来决定 WXML 的组件应该怎么显示。” “我们的 WXSS 具有 CSS 大部分特性…我们对 CSS 进行了扩充以及修改。”基于 CSS2 还是 CSS3?大部分是哪些部分?是否支持 CSS3 里的动画?不得而知。

小程序原理就是用 JS 调用 -> 微信浏览器 -> 底层 Native 组件,和React Native非常类似。

  • 3.Weex 【Vue】

Weex 是个由国人主导的,阿里巴巴所开源的一个跨平台框架,就和我括号里标注的一样,在之前的南京的 JSConf 上,尤雨溪(Vue 的作者)宣布联手 Weex,现在的 Weex 可以称为 Vue-Native 啦。

write once run everywhere && Native Speed in Native Platform。

JS 和 Native 的爱恨纠缠

基本的架构如图所示:

JS 和 Native 的爱恨纠缠

weex 能让一套代码能做成 native 级别的app,主要是做了三件事:

-1.在本地用一个叫做 transformer 的工具把这套代码转成纯 JavaScript 代码

-2.在客户端运行一个 JavaScript 引擎,随时接收 JavaScript 代码

-3.在客户端设计一套 JS Bridge,让 native 代码可以和 JavaScript 引擎相互通信

当然这样的解读也是比较片面的,具体的实现因为篇幅有限这边也不一一展开,各位有兴趣的可以去官网查看,这个项目目前已经开源。

最后:

身为一个 Native 开发者我们怎么办?

这其实是一个很开放的问题,每个人的见解都不一样。

每一个新技术的诞生都有好处,也有坏处,也许他的快速开发特性吸引了你,可是你没看到背后调试的问题,或者很多地方存在着坑。

像 Android 刚发布的时候,有部分开发者选择留在了塞班,而曾经手机的巨头 Nokia 也已经几度易主了。

但是 Windows Phone 刚出来,确实觉得很不错,系统流程简介,可是结果目前连市场都不是很成熟 用户量也少。

技术本来就没有好坏之分,需求决定技术。作为开发者的我们更多的情况下是身不由己的,如果需求 1 个月内迭代项目,要求双版本,人员和时间都有限的情况下,上述的跨平台无疑是最合适的技术方案,或许领导看了微信小程序,觉得可以搞,那我们也必须去学习,至少是去做到了解这个新的技术。

具体的内容,还是要基于真正的实践,网上的资料不能以偏概全,别人的经验也不能套用套自己身上,希望这篇文章能起到一点抛砖引玉的作用,这个东西好不好用?能不能用?试一下不就知道了么?

程序员是一个需要不断拥抱新的技术的职业,但也不要盲目的学习新技术,要了解原理,才能融汇贯通结合到实际工作之中。

原文  http://danny-lau.com/2017/01/17/js-和-native-的爱恨纠缠/
正文到此结束
Loading...