JSbridge原理与实现简析
JSBridge定义定义:正如其命名一样,JSBridge就相当于js与native之间进行全双工通信的一座桥梁,其内部定义了一套用于js与native进行通信的规范(包括协议、方法、传参及回调等);用途:JSBridge可以桥连js与native的通信,从而使基于容器的web开发和优化成为可能,如比较火的hybrid app技术;能够提升页面性能,丰富页面功能等;JSBridge原理简析框架简析
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
调用Native
-
url 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
实例对象是否存在;
参考文献
更多推荐
所有评论(0)