WebView 与 Native 的技术选型

LEGO-SDK

LEGO-SDK 是一个轻量级的 WebView 增强库,点击链接以查看使用方法以及开源代码

前言

这篇文章是《LEGO-SDK 轻量级的跨平台 Web App 引擎》的后续小记。
本文对常见的 WebView 与 Native 交互方式进行描述,并详解 LEGO-SDK 的技术选型。

Javascript

在开始描述交互方式前,我们不得不先普及一下 Javascript 的冷知识。

来历

1995 年,它作为网景浏览器(Netscape Navigator)的一部分首次发布,网景给这个新语言命名为 LiveScript。一年后,为了搭上当时媒体热炒 Java 的顺风车,临时改名为了 JavaScript (当然,Java 和 JavaScript 的关系,就和雷锋和雷锋塔一样 —— 并没有什么关系)

ECMAScript

1996 年,网景将 JavaScript 提交给 ECMA International(欧洲计算机制造商协会) 进行标准化,并最终确定出新的语言标准,它就是 ECMAScript。自此,ECMAScript 成为所有 JavaScript 实现的基础,不过,由于 JavaScript 名字的历史原因和市场原因(很显然 ECMAScript 这个名字并不令人喜欢……),现实中我们只用 ECMAScript 称呼标准,平时都还是使用 JavaScript 来称呼这个语言。

ES3 ~ ES5

不过,JavaScript 开发者们并不怎么在乎这些,因为在诞生之后的 15 年里,ECMAScript 并没有多少变化,而且现实中的很多实现都已经和标准大相径庭。其实在第一版的 ECMAScript 发布后,很快又跟进发布了两个版本,但是自从 1999 年 ECMAScript 3 发布后,十年内都没有任何改动被成功添加到官方规范里。取而代之的,是各大浏览器厂商们争先进行自己的语言拓展,web 开发者们别无选择只能去尝试并且支持这些 API。即使是在 2009 年 ECMAScript 5 发布之后,仍然用了数年这些新规范才得到了浏览器的广泛支持,可是大部分开发者还是写着 ECMAScript 3 风格的代码,并不觉得有必要去了解这些规范。

目前,iOS 与 Android 的 WebView 支持 ES5。

ES6

到了 2012 年,事情突然开始有了转变。大家开始推动停止对旧版本 IE 浏览器的支持,用 ECMAScript 5 (ES5) 风格来编写代码也变得更加可行。与此同时,一个新的 ECMAScript 规范也开始启动。到了这时,大家开始逐渐习惯以对 ECMAScript 规范的版本支持程度来形容各种 JavaScript 实现。在正式被指名为 ECMAScript 第 6 版 (ES6) 之前,这个新的标准原本被称为 ES.Harmony(和谐)。2015 年,负责制定 ECMAScript 规范草案的委员会 TC39 决定将定义新标准的制度改为一年一次,这意味着每个新特性一旦被批准就可以添加,而不像以往一样,规范只有在整个草案完成,所有特性都没问题后才能被定稿。因此,ECMAScript 第 6 版在六月份公布之前,又被重命名为了 ECMAScript 2015(ES2015)

当前,只有 Android 支持部分 ES6 语法,但是,开发者可以使用 Babel 进行兼容处理。

WebView

WebView 的知识也需要普及一下。

WebView 在 UIKit 和 Android UI 中都存在。其中,iOS 的 WebView 又分为 UIWebView 和 WKWebView。 Android 中的 WebView 则统称 WebView,Android 4.4 以前的版本,使用存在缺陷的 WebKit 内核,之后的版本使用 Blink 内核。

UIWebView / iOS

UIWebView 使用 WebKit 内核,Javascript 引擎使用 JavascriptCore。

在 iOS8 之前,Apple 只允许开发者使用 UIWebView 加载 Web 页面,并且不允许开发者自行封装 Javascript 引擎。UIWebView 以其内存占用极高,渲染超慢,Javascript 执行效率超低著称(然而,Safari 并不是使用UIWebView)。同时,UIWebView 还存在 leaks 的风险。

但是,UIWebView 还是有其用武之处的。由先,UIWebView 是一个经长期验证的 WebView,其周边已经存在不少优秀的开源库可供使用。其次,由于应用较早,BUG也较少,非常适合追求稳定性的应用使用。

WKWebView / iOS

WKWebView 同样使用 WebKit 内核,但是 Javascript 引擎使用 Nitro。

在 iOS8 开始,Apple 就允许开发者使用 WKWebView 了(Safari 正是使用该 UI)。WKWebView 自称拥有 60 FPS 的渲染、刷新速度,内存占用仅为 UIWebView 的 20%(确实如此),Javascript 执行效率是 JavascriptCore 的数倍(确实很快)。并且,WKWebView leaks 很稀有。

不过,WKWebView 对于开发者而言,还是** 太 **陌生了。

WKWebView 基础的方法与 UIWebView 是一致的,但 delegate 方法则作了完全的改造。 同时,WKWebView 再也不能使用 JSProtocol / JavascriptCore 进行交互了。

WebView / Android

WebView 在 Android 中有如干面包,虽然很难吃,但是,饿的时候,你还是不得不吃。

WebView 在 Android 中的渲染效率不高,Javascript 的执行速度也比 iOS 慢一大截(CPU性能相同情况下)。不过,Android 的 WebView 兼容性还是可以的,对 ES5/6 的支持最为及时。总体而言 Android WebView 是可以使用的,但自从爆出 Javascript 可以执行任意原生代码的漏洞后,业界对其则是又爱又恨。

JavascriptBridge

要使 Javascript 与 Native 进行交互,需要解决两个问题。

  • 捕捉 Javascript 发送到 Native 的事件
  • 从 Native 发送事件到 Javascript

同时,我们还需要解决一些使用上的问题。

  • 将指定的 Javascript 代码注入至目标网页
  • 在网页执行前就注入代码(即是网页可以在任意位置调用指定脚本)

Handle Request

一种传统的方式是捕捉请求。

在 iOS WebView 中具体的方法是:
1.实现 WebView 的 delegate,然后实现 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 回调。
2.拿到 request 并 return false。
3.接下来对 request 中的 Path 或者 HTTPBody 进行处理,得到交互信息。
4.处理请求,并使用[[UIWebView new] stringByEvaluatingJavaScriptFromString:@"xxx"]发送结果至 WebView。

在 Android 中也有类似的方法,在这里不再详尽解析。
这种方法的优点是简单快捷,只需要在 Web 中建立一个 iframe 标签,并将请求发送至该标签即可。
但是缺点也很明显,request 的发送需要经过多层处理,效率很低,并且,有可能因为某些原因导致请求丢失。
在开源库 JavascriptBridge 中也有类似的实现。

NSURLProtocol

另一种方式是使用 NSURLProtocol
使用 NSURLProtocol 可以捕捉到当前应用的所有网络请求(WKWebView除外),不管这个请求是使用 NSURLConnection 还是 NSURLSession 再或是 UIWebView 发出的。
因此,我们可以拦截这个请求,并修改其返回的结果。
在 WebView 中,只需要发送一个 Ajax 请求,即可达到目的。

在 Android 中,有类似的 WebView 方法。
这种方法优点是效率比 Handle Request 高,但仍然逃不出 Request 的陷阱。同时,这种方法对于 WKWebView 是无效的(除非使用 GCDWebServers 进行请求代理)。

JavascriptCore

还有一种方式是使用 JavascriptCore,自 iOS7 开始,苹果允许开发者使用 JavascriptCore 将代码注入 WebView,这种方式效率非常高,并且交互起来非常方便。
使用 JavascriptCore 可以将 JS 对象与 OC 对象进行无缝的转换。
具体用法,请戳 http://nshipster.cn/javascriptcore/

在 Android 中,则是使用 JavascriptInterface 实现的,但是Android 4.2 以下有漏洞,慎用。

这种方法优点是效率很高,使用方便,但是不支持 WKWebView。

WKUserContentController

如果使用 WKWebView,可以使用 WKUserContentController 进行交互。

1.使用 - (void)addUserScript:(WKUserScript *)userScript 注入代码
2.使用 - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name 捕捉请求
3.使用 [[WKWebView new] evaluateJavaScript:@"" completionHandler:nil]; 发送结果至 WebView

这种方法优点是支持 WKWebView,但是不支持同步请求,只能异步返回结果。

console.log 注入

在 Android 中,由于存在 JavascriptInterface 漏洞,我们需要避免使用该方式。
1.将高风险接口清除。

removeJavascriptInterface("searchBoxJavaBridge_");
removeJavascriptInterface("accessibility");
removeJavascriptInterface("accessibilityTraversal");

2.继承 WebChromeClient,重写 public boolean onConsoleMessage(ConsoleMessage consoleMessage) 方法。
3.在consoleMessage中捕捉请求,并使用 evaluateJavascript()返回结果至WebView。

通过该方法获取请求,可以绕过JavascriptInterface漏洞,Web 调用方法比较简单。

Javascript 指定代码注入

在业务上,我们需要将代码注入至 Web 页面最顶部,以便开发者可以在任意位置发起请求。
如上文所述,UIWebView 可以使用 JSProtocol 的方式,WKWebView 可以使用 addUserScript 的方式。
而 Android 则需要特殊处理,Android 仍然需要使用 JavascriptInterface,但是,该对象不包含 Context,这样可以避免安全问题。

class LGOJavaScriptBridge {

    LGOJavaScriptBridge() {}

    @JavascriptInterface
    public String bridgeScript() {
        return "Your script.";
    }

}
...
addJavascriptInterface(new LGOJavaScriptBridge(), "JSBridge");

Web 开发者只需要在网页头部添加 script 即可。

<script>JSBridge && eval(JSBridge.bridgeScript())</script>

LEGO-SDK

LEGO-SDK 在 iOS 中,对于 UIWebView 使用 JavascriptCore 进行封装,对于 WKWebView 使用 WKUserContentController 进行封装,对于 Android 则使用 console.log 注入进入封装。

如果你有更好的想法,欢迎提出。
本文只是一些经验之谈,如果有不正确的地方,也欢迎指点评批。

出处引用

http://huangxuan.me/2015/09/22/js-version/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • 前言 Web 页面中的 JS 与 iOS Native 如何交互是每个 iOS 猿必须掌握的技能。而说到 Nati...
    幽城88阅读 2,136评论 1 8
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,612评论 4 59
  • 前言 关于UIWebView的介绍,相信看过上文的小伙伴们,已经大概清楚了吧,如果有问题,欢迎提问。 本文是本系列...
    CoderLF阅读 8,853评论 2 12
  • Hello World! 你好,世界!
    Bravelee阅读 370评论 0 2