赞
踩
webview是一个嵌入式的浏览器,我们平常使用频率最高的就是客户端内嵌的webview
特点:运行在webview中的JS代码有能力调用原生的系统API,没有传统浏览器沙箱的限制。
上面说了,运行在webview中的JS代码有能力调用原生的系统API,那么具体是怎么实现的呢?
核心就是JSBridge。
当我们在native内打开webview网页,native会在全局的window下,为我们注入一个Bridge。这个Bridge里面,会包含我们与native交互的各种方法、比如判断第三方App是否安装、获取网络信息等等功能。
const bridge = window.JSBridge;
console.log(bridge.getNetInfomation());
Web端和Native可以类比于Client/Server模式,Web端调用原生接口时就如同Client向Server端发送一个请求类似,JSBridge在此充当类似于HTTP协议的角色,实现JSBridge主要是两点:
首先来说Native端调用Web端,这个比较简单,JavaScript作为解释性语言,最大的一个特性就是可以随时随地地通过解释器执行一段JS代码,所以可以将拼接的JavaScript代码字符串,传入JS解析器执行就可以,JS解析器在这里就是webView。
iOS
// Swift
webview.stringByEvaluatingJavaScriptFromString("Math.random()")
// OC
[webView stringByEvaluatingJavaScriptFromString:@"Math.random();"];
Android
mWebView.evaluateJavascript("javascript: 方法名('参数,需要转为字符串')", new ValueCallback() {
@Override
public void onReceiveValue(String value) {
//这里的value即为对应JS方法的返回值
}
});
目前javascript和客户端(后面统称native)交互的常见方式有两种
这里的URL Scheme既可以单独和原生通信,JSBridge实现的原理也是通过拦截URL Schema
我们可以自定义JSBridge通信的URL Schema,比如:jsbridge://showToast?text=hello
Native加载WebView之后,Web发送的所有请求都会经过WebView组件,所以Native可以重写WebView里的方法,拦截Web发起的请求,我们对请求的格式进行判断:
如果符合我们自定义的URL Schema,对URL进行解析,拿到相关操作、操作,进而调用原生Native的方法
如果不符合我们自定义的URL Schema,我们直接转发,请求真正的服务
Web发送URL请求的方法有这么几种:
这些方法,a标签需要用户操作,location.href可能会引起页面的跳转丢失调用,发送ajax请求Android没有相应的拦截方法,所以使用iframe.src是经常会使用的方案
安卓提供了shouldOverrideUrlLoading方法拦截
IOSUIWebView使用shouldStartLoadWithRequest,
IOSWKWebView则使用decidePolicyForNavigationAction
这种方式从早期就存在,兼容性很好,但是由于是基于URL的方式,长度受到限制而且不太直观,数据格式有限制,而且建立请求有时间耗时。所有有了JSBridge
对于Webview中发起的网络请求,Native都有能力去捕获/截取/干预。所以JSBridge的核心就是设计一套url方案,让Native可以识别,从而做出响应,执行对应的操作就完事。
例如,正常的网络请求可能是: https://img.alicdn.com/tps/TB17ghmIFXXXXXAXFXXXXXXXXXX.png
我们可以自定义协议,改成jsbridge://methodName?param1=value1¶m2=value2。
Native拦截jsbridge开头的网络请求,做出对应的动作。
最常见的做法就是创建一个隐藏的iframe来实现通信。
App将Native的相关接口注入到JS的window对象中,一般来说这个对象内的方法名与Native相关方法名是相同的,Web端就可以直接在全局window下使用这个全局JS对象,进而调用原生端的方法。
Android(4.2+)提供了addJavascriptInterface注入:
// 注入全局JS对象
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");
class NativeBridge {
private Context ctx;
NativeBridge(Context ctx) {
this.ctx = ctx;
}
// 增加JS调用接口
@JavascriptInterface
public void showNativeDialog(String text) {
new AlertDialog.Builder(ctx).setMessage(text).create().show();
}
}
在Web端直接调用这个方法即可:
window.NativeBridge.showNativeDialog('hello');
上面已经说到了Native、Web间双向通信的两种方法,但站在一端而言还是一个单向通信的过程 ,比如站在Web的角度:Web调用Native的方法,Native直接相关操作但无法将结果返回给Web,但实际使用中会经常需要将操作的结果返回,也就是JS回调。
所以在对端操作并返回结果,有输入有输出才是完整的调用,那如何实现呢?
其实基于之前的单向通信就可以实现,我们在一端调用的时候在参数中加一个callbackId标记对应的回调,对端接收到调用请求后,进行实际操作,如果带有callbackId,对端再进行一次调用,将结果、callbackId回传回来,这端根据callbackId匹配相应的回调,将结果传入执行就可以了。
DSBridge,主要通过注入API的形式,DSBridge for Android、DSBridge for IOS
JsBridge,主要通过拦截URL Schema,JsBridge
以DSBridge-Android为例:
// Web端代码 <body> <div> <button id="showBtn">获取Native输入,以Web弹窗展现</button> </div> </body> // 引入SDK <script src="https://unpkg.com/dsbridge@3.1.3/dist/dsbridge.js"></script> <script> const showBtn = document.querySelector('#showBtn'); showBtn.addEventListener('click', e => { // 注意,这里代码不同:SDK在全局注册了dsBridge,通过call调用Native方法 dsBridge.call('getNativeEditTextValue', '', value => { window.alert('Native输入值' + value); }) }); </script>
// Android代码 // 使用dwebView替换原生webView dwebView.addJavascriptObject(new JsApi(), null); class JSApi { private Context ctx; public JSApi (Context ctx) { this.ctx = ctx; } @JavascriptInterface public void getNativeEditTextValue(Object msg, CompletionHandler<String> handler) { String value = ((MainActivity)ctx).editText.getText().toString(); // 通过handler将value传给Web端,实现回调的JSB调用 handler.completed(value); } }
WKWebView在IOS8发布时,也随之一起诞生。在这之前IOS端一直使用的是UIWebView。
JavaScript 是运行在一个单独的 JS Context 中(例如,WebView 的 Webkit 引擎、JSCore)。由于这些Context 与原生运行环境的天然隔离,我们可以将这种情况与 RPC(Remote Procedure Call,远程过程调用)通信进行类比,将 Native 与 JavaScript 的每次互相调用看做一次 RPC 调用。如此一来我们可以按照通常的 RPC 方式来进行设计和实现。
在 JSBridge 的设计中,可以把前端看做 RPC 的客户端,把 Native 端看做 RPC 的服务器端,从而 JSBridge 要实现的主要逻辑就出现了:通信调用(Native 与 JS 通信) 和 句柄解析调用。(如果你是个前端,而且并不熟悉 RPC 的话,你也可以把这个流程类比成 JSONP 的流程)
Native 和 Javascript 通信的原理是 JSBridge 实现的核心,实现方式可以各种各样,但是万变不离其宗。这里,笔者推荐的实现方式如下:
JavaScript 调用 Native 推荐使用 注入 API 的方式(iOS6 忽略,Android 4.2以下使用 WebViewClient 的 onJsPrompt 方式)。
Native 调用 JavaScript 则直接执行拼接好的 JavaScript 代码即可。
也可以将URL SCHEME的Restful形式的接口参数格式同注入API方式相结合,做到接口调用方式上的兼容(只改动底层实现)
https://zhuanlan.zhihu.com/p/58691238
https://www.cnblogs.com/songyao666/p/14540073.html
https://github.com/marcuswestin/WebViewJavascriptBridge
https://github.com/lzyzsd/JsBridge
https://www.jianshu.com/p/be491bfbca0d
https://blog.csdn.net/qq_30868289/article/details/104233030
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。