移動混合開發中的 JSBridge
來源:https://mp.weixin.qq.com/s/I812Cr1_tLGrvIRb9jsg-A
【導讀】關於 JSBridge,絕大多數同學最早遇到的是微信的 WeiXinJSBridge(現在被封裝成 JSSDK),各種 Web 頁面可以通過 Bridge 呼叫微信提供的一些原生功能,為使用者提供相關的功能。其實,JSBridge 很早就出現在軟體開發中,在一些桌面軟體中很早就運用了這樣的形式,多用在通知、產品詳情、廣告等模組中,然後這些模組中,使用的是 Web UI,而相關按鈕點選後,呼叫的是 Native 功能。現在移動端盛行,不管是 Hybrid 應用,還是 React-Native 都離不開 JSBridge,當然也包括在國內舉足輕重的微信小程式。那麼,JSBridge 到底是什麼?它的出現是為了什麼?它究竟是怎麼實現的?在這篇文章中,會在移動混合開發的範疇內,將給大家帶來 JSBridge 的深入剖析。
1 前言
有些童鞋聽到 JSBridge 這個名詞,就是覺得非常高上大,有了它 Web 和 Native 可以進行互動,就像『進化藥水』,讓 Web 搖身一變,成為移動戰場的『上將一名』。其實並非如此,JSBridge 其實真是一個很簡單的東西,更多的是一種形式、一種思想。
2 JSBridge 的起源
為什麼是 JSBridge ?而不是 PythonBridge 或是 RubyBridge ?
當然不是因為 JavaScript 語言高人一等(雖然斯坦福大學已經把演算法導論的語言從 Java 改成 JavaScript,小得意一下,嘻嘻),主要的原因還是因為 JavaScript 主要載體 Web 是當前世界上的 最易編寫
因此,開發維護成本 和 更新成本 較低的 Web 技術成為混合開發中幾乎不二的選擇,而作為 Web 技術邏輯核心的 JavaScript 也理所應當肩負起與其他技術『橋接』的職責,並且作為移動不可缺少的一部分,任何一個移動作業系統中都包含可執行 JavaScript 的容器,例如 WebView 和 JSCore。所以,執行 JavaScript 不用像執行其他語言時,要額外新增執行環境。因此,基於上面種種原因,JSBridge 應運而生。
PhoneGap(Codova 的前身)作為 Hybrid 鼻祖框架,應該是最先被開發者廣泛認知的 JSBridge 的應用場景;而對於 JSBridge 的應用在國內真正興盛起來,則是因為殺手級應用微信的出現,主要用途是在網頁中通過 JSBridge 設定分享內容。
移動端混合開發中的 JSBridge,主要被應用在兩種形式的技術方案上:
-
基於 Web 的 Hybrid 解決方案:例如微信瀏覽器、各公司的 Hybrid 方案
-
非基於 Web UI 但業務邏輯基於 JavaScript 的解決方案:例如 React-Native
【注】:微信小程式基於 Web UI,但是為了追求執行效率,對 UI 展現邏輯和業務邏輯的 JavaScript 進行了隔離。因此小程式的技術方案介於上面描述的兩種方式之間。
3 JSBridge 的用途
JSBridge 簡單來講,主要是 給 JavaScript 提供呼叫 Native 功能的介面,讓混合開發中的『前端部分』可以方便地使用地址位置、攝像頭甚至支付等 Native 功能。
既然是『簡單來講』,那麼 JSBridge 的用途肯定不只『呼叫 Native 功能』這麼簡單寬泛。實際上,JSBridge 就像其名稱中的『Bridge』的意義一樣,是 Native 和非 Native 之間的橋樑,它的核心是 構建 Native 和非 Native 間訊息通訊的通道,而且是 雙向通訊的通道。
所謂 雙向通訊的通道:
-
JS 向 Native 傳送訊息 : 呼叫相關功能、通知 Native 當前 JS 的相關狀態等。
-
Native 向 JS 傳送訊息 : 回溯呼叫結果、訊息推送、通知 JS 當前 Native 的狀態等。
這裡有些同學有疑問了:訊息都是單向的,那麼呼叫 Native 功能時 Callback 怎麼實現的?
對於這個問題,在下一節裡會給出解釋。
4 JSBridge 的實現原理
JavaScript 是執行在一個單獨的 JS Context 中(例如,WebView 的 Webkit 引擎、JSCore)。由於這些 Context 與原生執行環境的天然隔離,我們可以將這種情況與 RPC(Remote Procedure Call,遠端過程呼叫)通訊進行類比,將 Native 與 JavaScript 的每次互相呼叫看做一次 RPC 呼叫。如此一來我們可以按照通常的 RPC 方式來進行設計和實現。
在 JSBridge 的設計中,可以把前端看做 RPC 的客戶端,把 Native 端看做 RPC 的伺服器端,從而 JSBridge 要實現的主要邏輯就出現了:通訊呼叫(Native 與 JS 通訊) 和 控制代碼解析呼叫。(如果你是個前端,而且並不熟悉 RPC 的話,你也可以把這個流程類比成 JSONP 的流程)
通過以上的分析,可以清楚地知曉 JSBridge 主要的功能和職責,接下來就以 Hybrid 方案 為案例從這幾點來剖析 JSBridge 的實現原理。
4.1 JSBridge 的通訊原理
Hybrid 方案是基於 WebView 的,JavaScript 執行在 WebView 的 Webkit 引擎中。因此,Hybrid 方案中 JSBridge 的通訊原理會具有一些 Web 特性。
4.1.1 JavaScript 呼叫 Native
JavaScript 呼叫 Native 的方式,主要有兩種:注入 API 和 攔截 URL SCHEME。
4.1.1.1 注入API
注入 API 方式的主要原理是,通過 WebView 提供的介面,向 JavaScript 的 Context(window)中注入物件或者方法,讓 JavaScript 呼叫時,直接執行相應的 Native 程式碼邏輯,達到 JavaScript 呼叫 Native 的目的。
對於 iOS 的 UIWebView,例項如下:
1 |
JSContext *context = [uiWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; |
前端呼叫方式:
1 |
window.postBridgeMessage(message); |
對於 iOS 的 WKWebView 可以用以下方式:
1 |
@interface WKWebVIewVC ()<WKScriptMessageHandler> |
前端呼叫方式:
1 |
window.webkit.messageHandlers.nativeBridge.postMessage(message); |
對於 Android 可以採用下面的方式:
1 |
public class JavaScriptInterfaceDemoActivity extends Activity { |
前端呼叫方式:
1 |
window.nativeBridge.postMessage(message); |
在 4.2 之前,Android 注入 JavaScript 物件的介面是 addJavascriptInterface,但是這個介面有漏洞,可以被不法分子利用,危害使用者的安全,因此在 4.2 中引入新的介面 @JavascriptInterface(上面程式碼中使用的)來替代這個介面,解決安全問題。所以 Android 注入對物件的方式是 有相容性問題的。(4.2 之前很多方案都採用攔截 prompt 的方式來實現,因為篇幅有限,這裡就不展開了。)
4.1.1.2 攔截 URL SCHEME
先解釋一下 URL SCHEME:URL SCHEME是一種類似於url的連結,是為了方便app直接互相呼叫設計的,形式和普通的 url 近似,主要區別是 protocol 和 host 一般是自定義的,例如: qunarhy://hy/url?url=http://ymfe.tech,protocol 是 qunarhy,host 則是 hy。
攔截 URL SCHEME 的主要流程是:Web 端通過某種方式(例如 iframe.src)傳送 URL Scheme 請求,之後 Native 攔截到請求並根據 URL SCHEME(包括所帶的引數)進行相關操作。
在時間過程中,這種方式有一定的 缺陷:
-
使用 iframe.src 傳送 URL SCHEME 會有 url 長度的隱患。
-
建立請求,需要一定的耗時,比注入 API 的方式呼叫同樣的功能,耗時會較長。
但是之前為什麼很多方案使用這種方式呢?因為它 支援 iOS6。而現在的大環境下,iOS6 佔比很小,基本上可以忽略,所以並不推薦為了 iOS6 使用這種 並不優雅 的方式。
【注】:有些方案為了規避 url 長度隱患的缺陷,在 iOS 上採用了使用 Ajax 傳送同域請求的方式,並將引數放到 head 或 body 裡。這樣,雖然規避了 url 長度的隱患,但是 WKWebView 並不支援這樣的方式。
【注2】:為什麼選擇 iframe.src 不選擇 locaiton.href ?因為如果通過 location.href 連續呼叫 Native,很容易丟失一些呼叫。
4.1.2 Native 呼叫 JavaScript
相比於 JavaScript 呼叫 Native, Native 呼叫 JavaScript 較為簡單,畢竟不管是 iOS 的 UIWebView 還是 WKWebView,還是 Android 的 WebView 元件,都以子元件的形式存在於 View/Activity 中,直接呼叫相應的 API 即可。
Native 呼叫 JavaScript,其實就是執行拼接 JavaScript 字串,從外部呼叫 JavaScript 中的方法,因此 JavaScript 的方法必須在全域性的