JavaScript是運行在一個單獨的 JS Context中(例如: webview的webkit引擎,JSCore)
本位主要總結下 JSBridge 前端實現原理,來自工作中的總結,安卓/ios代碼僅為示意JSBridge 是廣為流行的Hybrid 開發中JS和Native一種通信方式,簡單的説,JSBridge就是定義Native和JS的通信,Native只通過一個固定的橋對象調用JS,JS也只通過固定的橋對象調用native,
jsBridge兩種交互方式注入api、url schema、重寫h5全局方法
注入api的形式
安卓操作方法
-
native調用js
// 安卓4.4版本之前,無法獲取返回值 // mWebView = new WebView(this); // 即當前webview對象 mWebView.loadUrl("javascript: 方法名('參數,需要轉為字符串')") // 安卓4.4及以後 // webView.evaluateJavascript("javascript:if(window.callJS){window.callJS('" + str + "');}", new ValueCallback<String>() { mWebView.evaluateJavascript("javascript: 方法名,參數需要轉換為字符串", new ValueCallback() { @Override public void onReceiveValue(String value) { // 這裏的value即為對應JS方法的返回值 } }) // js 在全局window上聲明一個函數供安卓調用 window.callAndroid = function() { console.log('來自中h5的方法,供native調用') return "來自h5的返回值" } /** 總結: 1. 4.4 之前Native通過loadUrl來調用js方法,只能讓某個js方法執行,但是無法獲取該方法的返回值 2. 4.4 之後,通過evaluateJavaScript異步調用js方法,並且能在onReceive中拿到返回值 3. 不適合傳輸大量數據 4. mWebView.loadUrl("javascript: 方法名") 函數需在UI線程運行,因為mWebView為UI控件,會阻塞UI線程 */ -
JS調用Native
// 安卓環境配置 WebSettings webSettings = mWebView.getSettings(); // Android容器允許js腳本,必須要 webSettings.setJavaScriptEnabled(true); // Android 容器設置僑連對象 mWebView.addJavascriptInterface(getJSBridge(), "JSBridge"); // Android中JSBridge的業務代碼 private Object getJSBridge() { Object insterObj = new Object() { @JavascriptInterface public String foo() { // 此處執行 foo bridge的業務代碼 return "foo" // 返回值 } @JavascriptInterface public String foo2(final String param) { // 此處執行 foo2 方法 bridge的業務代碼 return "foo2" + param; } } return inserObj; } // js調用原生的代碼 // JSBridge 通過addJavascriptInterface已被注入到 window 對象上了 window.JSBridge.foo(); // 返回 'foo' window.JSBridge.foo2(); // 返回 'foo2:test' // 注意:在安卓4.2之前 addJavascriptInterface有風險,hacker可以通過反編譯獲取Native註冊的Js對象,然後在頁面通過反射Java的內置 靜態類,獲取一些敏感的信息和破壞
ios 操作方法
-
js 調用native
// 注意:ios7 以前 js無法調用native方法,ios7之後可以引入第三方提供的 JavaScriptCore 庫 /* 總結: 1. ios7 才出現這種方式,在這之前js無法直接調用Native,只能通過JSBridge方式調用 2. JS 能調用到已經暴露的api,並且能得到相應返回值 3. ios原生本身是無法被js調用的,但是通過引入官方提供的第三方“JavaScriptCore”,即可開發api給JS調用 */ // WKWebview ios8之後才出現,js調用native方法 // ios 代碼配置 https://zhuanlan.zhihu.com/p/32899522 // js調用 window.webkit.messageHandlers.{name}.postMessage(msgObj); /* * 優缺點 ios開發自帶兩種webview控件 UIWebview(ios8 以前的版本,建議棄用)版本較老, 可使用JavaScriptCore來注入全局自定義對象 佔用內存大,加載速度慢 WKWebview 版本較新 加載速度快,佔用內存小 */ -
native 調用 js
// UIWebview [webView stringByEvaluatingJavaScriptFromString:@"方法名(參數);"]; // WKWebview [_customWebView evaluateJavaScript:[@"方法名(參數)"] completionHandler:nil]; -------------------- // js 調用 native // 引用官方庫文件 UIWebview(ios8 以前的版本,建議棄用) #import <JavaScriptCore/JavaScriptCore.h> // webview 加載完畢後設置一些js接口 -(void)webViewDidFinishLoad:(UIWebView *)webView{ [self hideProgress]; [self setJSInterface]; } -(void)setJSInterface{ JSContext *context =[_wv valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; // 註冊名為foo的api方法 context[@"foo"] = ^() { //獲取參數 NSArray *args = [JSContext currentArguments]; NSString *title = [NSString stringWithFormat:@"%@",[args objectAtIndex:0]]; //做一些自己的邏輯 返回一個值 'foo:'+title return [NSString stringWithFormat:@"foo:%@", title]; }; } window.foo('test'); // 返回 'foo:test'url scheme 介紹
url scheme是一種類似於url的鏈接,是為了方便app直接互相調用設計的:具體為:可以用系統的OpenURI打開類似與url的鏈接(可拼入參數),然後系統會進行判斷,如果是系統的url scheme,則打開系統應用,否則找看是否有app註冊中scheme,打開對應app,需要注意的是,這種scheme必須原生app註冊後才會生效,如微信的scheme為weixin://- 調用過程(如用
iframe.src),然後native用某種方法捕獲對應的url觸發事件,然後拿到當前觸發url,根據定好的協議(scheme://method/?params=xxx),然後native攔截該請求分析當前觸發了哪種方法,然後根據定義來實現 -
客户端捕獲url
- 安卓捕獲 url scheme:
shouldoverrideurlloading捕獲到url進行分析 - ios: 在
UIWebView WKWebview內發起的所有網絡請求,都可以通過 delegate函數在native層得到通知,通過shouldStartLoadWithRequest捕獲webview中觸發的url scheme
- 安卓捕獲 url scheme:
- 大致流程:
h5 --> 通過某種方式觸發一個url --> native捕獲到url,進行分析 -->原生做處理 --> 如果需要回調 native再調用h5的JSBridge對象傳遞迴調 - 缺點:速度可能稍慢一點,url長度會有限制,需要定義url結構解析較為複雜
-
相較於注入api形式有以下有優點:
- Android4.2 一下,addJavaScriptInterface方式有安全漏洞
- ios7以下,js無法調用native
- url scheme交互方式是一套現有的成熟方案,可以兼容各種版本
重寫prompt/alert等原生方法
- native會劫持webview
onJsAlert、onJsConfirm、onConsoleMessage、onJsPrompt並進行重寫,好像ios高版本對此方式做了限制
設計實現一個JSBridge
- 設計出一個native與js交互的
全局橋對象 - js如何調用native
- native如何調用js
- h5中api方法
send/register
// 名稱: JSBridge 掛在 window上的一個屬性
window.JSBridge = {
// ...其他屬性,比如:版本、app基礎信息
// 回調函數集合
_cbMap: {},
// js註冊方法,native主動發起調用
register(method, callback) {
const callbackId = method
this._cbMap[callbackId] = callback
},
// h5 調用native方法,調用時會將回調 id 存放到本地變量cbList中
send(method, data, callback) {
let callbackId
if (callback) {
callbackId = `${method}_${Date.now()}`
this._cbMap[callbackId] = callback
}
const params = {
method, // 和客户端約定好定為method字段,即bridge名稱
callback: callbackId,
data: {} // 業務參數
}
// 調用native將參數傳遞進去 ==> 通信方式以上三種可任意選擇
callNative(JSON.Stringify(params))
},
// native回調js的方法 obj: { 回調id, 回調數據 }
// 相當於native只調用此方法,參數為json字符串
handler(obj) {
const { callbackId, data } = JSON.parse(obj)
// 執行對應的回調函數即send傳進來的callback,如果要返回值,可再發一個send
this._cbMap[callbackId] && this._cbMap[callbackId].(data)
}
}
// 調用示例
// 主動發消息
JSBridge.send('ui.callNative', {}, (data) => data)
// 註冊在本地,被動接受客户端調用
JSBridge.register("ui.datatabupdate", (data) => data);
JSBridge.send內部的callNative的具體實現
-
url schema方式
// url schema 實現 // 變成字符串並編碼 var url = scheme://ecape(JSON.stringify(param)) // 使用內部創建好的iframe來觸發scheme(location.href = 可能會造成跳轉問題) var iframe = document.createElment('iframe'); iframe.src = url; document.head.appendChild(iframe); setTimeout(() => document.head.removeChild('iframe'), 200) -
api注入方式
// ios window.webkit.messageHandlers.{name}.postMessage(JSON.stringify(params)) // 安卓 window.{name}.{name}(JSON.stringify(params))
參考資料
- https://www.cnblogs.com/dailc...
- https://zhuanlan.zhihu.com/p/... ,JSBridgeDemo
- https://github.com/chemdemo/c...
- 安卓與js交互
- ZOO TTEAM JSBridge