本位主要總結下 JSBridge 前端實現原理,來自工做中的總結,安卓/ios代碼僅爲示意
JavaScript是運行在一個單獨的 JS Context中(例如: webview的webkit引擎,JSCore)
// 安卓4.4版本以前,沒法獲取返回值 // mWebView = new WebView(this); // 即當前webview對象 mWebView.loadUrl("javascript: 方法名('參數,須要轉爲字符串')") // 安卓4.4及之後 mWebView.evaluateJavascript("javascript: 方法名,參數須要轉換爲字符串", new ValueCallback() { @Override public void onReceiveValue(String value) { // 這裏的value即爲對應JS方法的返回值 } }) // 總結: 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; } // html 中 js調用原生的代碼 // JSBridge 經過addJavascriptInterface已被注入到 window 對象上了 window.JSBridge.foo(); // 返回 'foo' window.JSBridge.foo2(); // 返回 'foo2:test' 注意:在安卓4.2以前 addJavascriptInterface有風險,hacker能夠經過反編譯獲取Native註冊的Js對象,而後在頁面經過反射Java的內置 靜態類,獲取一些敏感的信息和破壞
// 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]; }; } // js 調用原生代碼 window.foo('test'); // 返回 'foo:test' // 注意: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.JSBridge.postMessage(msgObj); ios開發自帶兩種webview控件 UIWebview(ios8 之前的版本,建議棄用) 版本較老 可以使用JavaScriptCore來注入全局自定義對象 佔用內存大,加載速度慢 WKWebview 版本較新 加載速度快,佔用內存小 js使用全局對象window.webkit.messageHandlers.{NAME}.postMessage 來調用native的方法
JSBridge 是廣爲流行的Hybrid 開發中JS和Native一種通訊方式,簡單的說,JSBridge就是定義Native和JS的通訊,Native只經過一個固定的橋對象調用JS,JS也只經過固定的橋對象調用native,基本原理是:javascript
h5 --> 經過某種方式觸發一個url --> native捕獲到url,進行分析 -->原生作處理 --> native 調用h5的JSBridge對象傳遞迴調html
上面咱們看到native已經和js實現通訊,爲何還要經過url scheme 的這種jsBridge方法呢前端
weixin://
具體位置app不會註冊對應的scheme,而是由前端頁面經過某種方式觸發scheme(如用 iframe.src
),而後native用某種方法捕獲對應的url觸發事件,而後拿到當前觸發url
,根據定好的協議(scheme://method...
),分析當前觸發了哪一種方法,而後根據定義來實現java
1. 設計出一個native與js交互的`全局橋對象` 2. js如何調用native 3. native如何得知api被調用 4. 分析 url 參數和回調的格式 5. native如何調用js 6. h5中api方法的註冊以及格式
// 名稱: JSBridge 掛在 window上的一個屬性 var JSBridge = window.JSBridge || (window.JSBridge = {}); /** 該對象有以下方法: registerHandler(String, Function) 註冊本地 js 方法,註冊後 native可經過 JSBridge調用,註冊後會將方法註冊到本地變量 messageHandles中 sendHandler(String, JSON, Function) h5 調用原生開放的api,調用後實際上仍是本地經過 url scheme觸發,調用時會將回調 id 存放到本地變量responseCallbacks 中 _handleMessageFromNative h5 調用native以後的回調通知 參數爲 {reposeId: 回調id, responseData: 回調數據} */ var JSBridge = { // 註冊本地方法供原生調用 registerHandler: function(method, cb) { // 會將cb 放入 messageHandlers裏面,待原生調用 }, messageHandles: {}, // h5註冊方法集合,供native通知後回調調用 // h5 主動調用native,需生成惟一的callbackId sendHandler: function(mathod, data, succCb, errCb) { // 內部經過iframe src url scheme 向native發送請求 // 並將對應的回調註冊進 responseCallbacks // native 處理結束後將結果信息通知到h5 經過 _handleMessageFromNative // h5 拿到返回信息處理 responseCallbacks 裏對應的回調 }, responseCallbacks: {}, // 回調集合 // native 通知 h5 _handleMessageFromNative: function(message) { // 解析 message,而後根據通知類型執行 messageHandles 或 responseCallbacks裏的回調 } } /** 注意: 1. native 調用_handleMessageFromNative通知h5,參數爲 json 字符串 2. native 主動調用h5方法時 {methodName: api名, data, callbackId} methodName: 開放api的名稱 data: 原生處理後傳遞給 h5 參數 須要把回調函數的值 return 出去,供native拿到, 或者再發一個 bridge 回去,方法名是 methodNameSuccess,或者嚴禁掉,方法名爲native生產的callbackId */ 如: bridge.register("hupu.ui.datatabupdate", (name) => { if(name) { // 再發一個bridge通知原生tab更新成功,,,method 能夠爲native生成的 callbackId bridge.send('hupu.ui.datatabsuccess', {}) } });
// sendHandler 執行步驟 1. 判斷是否有回調函數,若是有,生成一個回調函數id,並將id,和對應的回調添加放入回調函數集合 responseCallbacks 中 2. 經過特定的參數轉換方法,將傳入的數據,方法名一塊兒拼接成一個 url scheme,以下: var param = { method: 'methodName', data: {xx: 'xx'}, success: 'successId', error: 'errorId' } // 變成字符串並編碼 var url = scheme://ecape(JSON.stringify(param)) 3. 使用內部建立好的iframe來觸發scheme(location.href = 可能會形成跳轉問題) ...建立iframe var iframe = document.createElment('iframe'); iframe.src = url; document.head.appendChild(iframe); setTimeout(() => document.head.removeChild('iframe'), 200)
native 如何得知 api 被調用
ios
shouldoverrideurlloading
捕獲到url進行分析window.prompt(url, '')
來觸發scheme,而後native經過重寫webviewClient
的 onJsPrompt
來獲取url,而後解析UIWebView WKWebview
內發起的全部網絡請求,均可以經過 delegate函數在native層獲得通知,經過 shouldStartLoadWithRequest
捕獲webview中觸發的url scheme
分析url參數和回調的格式git
native如何調用 js (參照上面的native執行js的方法)github
JSBridge._handleMessageFromNative(messageJSON)
,json格式:{responseId, reponseData}
JSBridge._handleMessageFromNative(param)
,param 格式爲 {methodName, data}
,因爲是異步不支持批量調用