JSBridge定义
- 定义:正如其命名一样,
JSBridge就相当于js与native之间进行全双工通信的一座桥梁,其内部定义了一套用于js与native进行通信的规范(包括协议、方法、传参及回调等); - 用途:
JSBridge可以桥连js与native的通信,从而使基于容器的web开发和优化成为可能,如比较火的hybrid app技术;能够提升页面性能,丰富页面功能等;
JSBridge原理简析6+
框架简析:
JSBridge框架其实主要由两部分组成:第一部分是Native调用js,主要用于消息推送、状态同步及回溯调用结果等;第二部分是js调用Native,主要用于调用系统api、事件监听及状态同步等;Native调用js- Android
Android有两种方式:4.4.0以前使用方法loadUrl——调用方便,无法获取回调结果,会刷新webview;4.4.0+使用方法evaluateScript提供更加高效完善的功能——可以获取返回值并且不刷新webview;
需要注意的是,这里可调用的方法是全局方法;# loadUrl mWebView.loadUrl("javascript: methodName(paramStr)"); # evaluateScript mWebView.evaluateScript("javascript: method(paramStr)", new ValueCallback<String>() { @override public void onReceiveValue() { // do something after receive js callback value } } - IOS
# UIWebView mWebView.stringByEvaluatingJavaScriptFromString("methodName(paramStr)"); #WKWebView mWebView.evaluateScript("methodName(paramStr)");
- Android
js调用Nativeurl schema拦截
h5和native约定一套通信协议作为通信基础,一般如下:
schema://methodName?params=xxx&cb=xxx;
其中schema为双方协商的协议名,methodName为js调用native的方法名,params为参数集字符串,cb为接收回调结果的js方法名;在h5中发起请求时,一般通过构建一个不可见的iframe发起请求;请求以约定的方式以url形式发送,native会拦截h5的所有请求(如进行长连接优化等),如果发现url中的协议名是约定的协议名(如jsbridge),则会解析其中的methodName、params及cb等信息。如下给出了简单实现:window.callId = 0; const callNative = (method, params = null, cb) => { const paramsObj = { data: params ? JSON.stringify(params) : null, } if (typeof cb === 'function') { const cbName = cb + window.callId++; window[cbName] = cb; paramsObj['cbName'] = cbName; } // 设定通信url供native拦截 const url = `jsbridge://${method}?${JSON.stringify(paramsObj)}`; const iframe = document.createElement('iframe'); iframe.src = url; iframe.style.width = 0; iframe.style.height = 0; iframe.frameborder = 0; iframe.style.display = 'none'; document.body.appendChild(iframe); setTimeout(() => { iframe.parentNode.removeChild(iframe); }, 100); }缺点:消息传输通过
url传输,因此传输数据长度受到限制;
prompt、alert、confirm拦截
一般通过prompt进行通信,其他实现与url schema拦截类似;native收到prompt事件后会通过onJsPrompt等类似事件对prompt做处理,从而获取js传入的method、params、cb等;function callNative(method, params, cb) { ... const url = `jsbridge://${method}?${JSON.stringify(paramsObj)}`; prompt(url); }缺点:
ios的UIWebkit不支持;注入JS上下文
这种方法一般是将需要供js调用的native方法通过实例对象的方式通过webview提供的方法注入到js全局上下文,这样位于webview内的h5页面中js可以直接调用native的实例方法;
注入方式:Android的webview通过addJ avascriptInterface;ios UIWebview通过JSContext;ios WKWebview通过scriptMessageHandler;
下面是来自文献2的Android客户端关于注入JS上下文的简单demo;首先,声明一个
NativeMethods的类,用于定义对外暴露给js调用的方法,格式如methodName(webview, args, cb);
然后定义一个JSBridge类:其中定义了两个静态方法和一个实例方法;register用于将nativeMethods类下的方法转为hashMap格式,便于查询;call是暴露给js的供js统一调用的方法;
最后在webview创建后注册对外方法并将JSBridge实例通过addJavascriptInterface注入到JS全局上下文中;public class MainActivity extends AppCompatActivity { private WebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWebView = (WebView) findViewById(R.id.mWebView); ... // 将 NativeMethods 类下面的提供给 js 的方法转换成 hashMap JSBridge.register("JSBridge", NativeMethods.class); // 将 JSBridge 的实例对象注入到 js 全局上下文中,名字为 _jsbridge,该实例对象下有 call 方法 mWebView.addJavascriptInterface(new JSBridge(mWebView), "_jsBridge"); } } public class NativeMethods { // 用来供 js 调用的方法 public static void methodName(WebView view, JSONObject arg, CallBack callBack) { } } public class JSBridge { private WebView mWebView; public JSBridge(WebView webView) { this.mWebView = webView; } private static Map<String, HashMap<String, Method>> exposeMethods = new HashMap<>(); // 静态方法,用于将传入的第二个参数的类下面用于提供给 javacript 的接口转成 Map,名字为第一个参数 public static void register(String exposeName, Class<?> classz) { ... if (!exposeMethods.containsKey(exposeName)) { exposeMethods.put(exposeName, getAllMethod(classz)); } } // 实例方法,用于提供给 js 统一调用的方法 @JavascriptInterface public String call(String methodName, String args) { ... } }根据上述代码可以看到注入到
js全局对象中的实例对象为_jsBridge;在h5中的调用如下:window.callId = 0; const callNative = (method, params = null, cb) => { const paramsObj = { data: params ? JSON.stringify(params) : null, } if (typeof cb === 'function') { const cbName = cb + window.callId++; window[cbName] = cb; paramsObj['cbName'] = cbName; } if (window._jsBridge) { window._jsBridge.call(method, JSON.stringify(paramsObj)); } else { // 兜底方案 const url = `jsbridge://${method}?${JSON.stringify(paramsObj)}`; prompt(url); }缺点:安卓4.2以下存在安全漏洞,可能会导致用户信息泄露;
JSBridge静态方法的call方法实现:public static String call(WebView webView, String urlString) { if (!urlString.equals("") && urlString!=null && urlString.startsWith("jsbridge")) { Uri uri = Uri.parse(urlString); String methodName = uri.getHost(); try { JSONObject args = new JSONObject(uri.getQuery()); JSONObject arg = new JSONObject(args.getString("data")); String cbName = args.getString("cbName"); if (exposeMethods.containsKey("JSBridge")) { HashMap<String, Method> methodHashMap = exposeMethods.get("JSBridge"); if (methodHashMap!=null && methodHashMap.size()!=0 && methodHashMap.containsKey(methodName)) { Method method = methodHashMap.get(methodName); if (method!=null) { method.invoke(null, webView, arg, new CallBack(webView, cbName)); } } } } catch (Exception e) { e.printStackTrace(); } } return null; }除此之外,
js调用native方法成功需要给h5响应的结果反馈,因此native端需要定义一个Callback类用于处理js调用成功的结果反馈;其本质还是native调用js方法,以下是安卓端使用evaluatecript方法实现的Callback类:public class CallBack { private String cbName; private WebView mWebView; public CallBack(WebView webView, String cbName) { this.cbName = cbName; this.mWebView = webView; } public void apply(JSONObject jsonObject) { if (mWebView!=null) { mWebView.post(() -> { mWebView.evaluateJavascript("javascript:" + cbName + "(" + jsonObject.toString() + ")", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { return; } }); }); } } }

JSBridge的引用
- 由
h5引用
由h5引用即使用对应的封装好的JSBridge的npm包,内部封装了js调用native方法的方法集;该方式能够保证调用时JSBridge一定存在;缺点是当有变更时,需要native与h5同时做变更进行兼容; - 由
native注入
即上边提到的将JSBridge实例对象注入到js全局上下文;该方式有利于保障API与Native的一致性,但是由于注入方法和时机受到限制,h5调用时总是要判断JSBridge实例对象是否存在;