js與jsBridge的通訊原理

第一次接觸到混合開發應該是在一年前,當時在做 ionic 和 Cordova(PhoneGap)專案的時候,這些框架在 web 基礎上包了一層 Native,然後透過 Bridge 技術使得 js 可以呼叫影片、位置、音訊等功能。目前手上負責的專案則是一個與自家 APP 互動的混合開發專案,於是在課餘時間就查了查相關的實現方案和原理。本文就是介紹這層 Bridge 的互動原理,主要描述了 js 與 ios 及 android 底層的通訊原理及 JSBridge 的封裝技術及除錯方法。

主要用途

JSBridge 簡單來講,主要是 給 JavaScript 提供呼叫 Native 功能的介面,讓混合開發中的前端部分可以方便地使用定位、攝像頭、系統相簿等 Native 功能。

但是 JSBridge 的用途肯定不只呼叫 Native 功能這麼簡單。實際上,JSBridge 就像其名稱中的

Bridge

的意義一樣,是 Native 和非 Native 之間的橋樑,它的核心是 構建

Native

非 Native

間訊息通訊的通道,而且是 雙向通訊的通道。

js與jsBridge的通訊原理

所謂 雙向通訊的通道:

JS 向 Native 傳送訊息 : 呼叫相關功能、通知 Native 當前 JS 的相關狀態等。

Native 向 JS 傳送訊息 : 回溯呼叫結果、訊息推送、通知 JS 當前 Native 的狀態等。

實現原理

主要分為兩個部分,分別是

JavaScript 呼叫 Native

Native 呼叫 JavaScript

JavaScript 呼叫 Native

主要有兩種實現方案,一種是

攔截 URL SCHEME

,另一種是

注入API讓js直接呼叫

攔截 URL SCHEME

什麼是 URL SCHEME

由於蘋果的 app 都是在沙盒中,相互是不能訪問資料的。但是蘋果還是給出了一個可以在 app 之間跳轉的方法:URL Scheme。URL SCHEME:URL SCHEME 是一種類似於 url 的連結,是為了方便 iosapp 直接互相跳轉設計的,形式和普通的 url 近似,主要區別是 protocol 和 host 一般是自定義的,例如: lefit://leoao/xxxx?xx=123,protocol 是 lefit,host 則是 leoao

實現流程

在 UIWebView 內發起的所有網路請求,都可以透過 delegate 函式在 Native 層得到通知。這樣,我們就可以在 UIWebView 內發起一個自定義的網路請求,攔截 URL SCHEME 的主要流程是:Web 端透過某種方式(例如 iframe。src)傳送 URL Scheme 請求,之後 Native 攔截到請求並根據 URL SCHEME(包括所帶的引數)進行相關操作。

在實際過程中,這種方式有一定的 缺陷:

使用 iframe。src 傳送 URL SCHEME 會有 url 長度的隱患。

建立請求,需要一定的耗時,比注入 API 的方式呼叫同樣的功能,耗時會較長。

但是之前為什麼很多方案使用這種方式呢?因為它 支援 iOS6。而現在的大環境下,iOS6 佔比很小,基本上可以忽略,所以並不推薦為了 iOS6 使用這種 並不優雅 的方式。

注 1

:有些方案為了規避 url 長度隱患的缺陷,在 iOS 上採用了使用 Ajax 傳送同域請求的方式,並將引數放到 head 或 body 裡。這樣,雖然規避了 url 長度的隱患,但是 WKWebView 並不支援這樣的方式。

注 2

:為什麼選擇 iframe。src 不選擇 locaiton。href ?因為透過 location。href 有個問題,就是如果我們連續多次修改 window。location。href 的值,在 Native 層只能接收到最後一次請求,前面的請求都會被忽略掉。

注入 API 讓 js 直接呼叫

注入 API 方式的主要原理是,透過 WebView 提供的介面,向 JavaScript 的 Context(window)中注入物件或者方法,讓 JavaScript 呼叫時,直接執行相應的 Native 程式碼邏輯,達到 JavaScript 呼叫 Native 的目的。

iOS

對於 iOS 的 UIWebView,例項如下:

JSContext *context = [uiWebView valueForKeyPath:@“documentView。webView。mainFrame。javaScriptContext”];context[@“postBridgeMessage”] = ^(NSArray

前端呼叫方式:

window。postBridgeMessage(message);

對於 iOS 的 WKWebView 可以用以下方式:

@interface WKWebVIewVC ()

前端呼叫方式:

window。webkit。messageHandlers。nativeBridge。postMessage(message);

Android

對於 Android 可以採用下面的方式:

public class JavaScriptInterfaceDemoActivity extends Activity { private WebView Wv; @Override public void onCreate(Bundle savedInstanceState) { super。onCreate(savedInstanceState); Wv = (WebView)findViewById(R。id。webView); final JavaScriptInterface myJavaScriptInterface = new JavaScriptInterface(this); Wv。getSettings()。setJavaScriptEnabled(true); Wv。addJavascriptInterface(myJavaScriptInterface, “nativeBridge”); // TODO 顯示 WebView } public class JavaScriptInterface { Context mContext; JavaScriptInterface(Context c) { mContext = c; } public void postMessage(String webMessage){ // Native 邏輯 } }}

前端呼叫方式:

window。nativeBridge。postMessage(message);

在 4。2 之前,Android 注入 JavaScript 物件的介面是

addJavascriptInterface

,但是這個介面有漏洞,可以被不法分子利用,危害使用者的安全,因此在 4。2 中引入新的介面

@JavascriptInterface

(上面程式碼中使用的)來替代這個介面,解決安全問題。所以 Android 注入對物件的方式是 有相容性問題的。

javascript 執行以下四種行為會被 webview 監聽到,箭頭後面是對應觸發的 Java 方法。由於 prompt 相對來說使用的很少,所以 4。2 之前很多方案都採用攔截 prompt 的方式來實現。

1、window。alert => onJSAlert2、window。confirm => onJSConfirm3、window。prompt => onJsPrompt4、window。location => shouldOverrideUrlLoading

Native 呼叫 JavaScript

Native 呼叫 JavaScript,其實就是

執行拼接 JavaScript 字串

,從外部呼叫 JavaScript 中的方法,因此 JavaScript 的方法必須在全域性的 window 上。(閉包裡的方法,JavaScript 自己都調用不了,更不用想讓 Native 去呼叫了)

iOS

對於 iOS 的 UIWebView,示例如下:

result = [uiWebview stringByEvaluatingJavaScriptFromString:javaScriptString];

對於 iOS 的 WKWebView,示例如下:

[wkWebView evaluateJavaScript:javaScriptString completionHandler:completionHandler];

Android

對於 Android,在 Kitkat(4。4)之前並沒有提供 iOS 類似的呼叫方式,只能用 loadUrl 一段 JavaScript 程式碼,來實現:

webView。loadUrl(“javascript:” + javaScriptString);

而 Kitkat 之後的版本,也可以用 evaluateJavascript 方法實現:

webView。evaluateJavascript(javaScriptString, new ValueCallback

:使用 loadUrl 的方式,並不能獲取 JavaScript 執行後的結果。

js與jsBridge的通訊原理