jsbridge
是隨著Hybrid App
的流行而產生的一種技術。那麼Hybrid App
是啥?Hybrid App
又稱混合App
,即同時使用了前端web技術(js,css,html)和原生native技術(java,kotlin,swfit,object-c)進行開發的移動應用。
混合開發的優缺點
- 優點:開發快,易更新,開發週期短,跨平臺
- 缺點:效能問題,相容性問題
常見的混合開發框架
- webview渲染:Cordova,uni-app
- 原生渲染:React Native,Weex,Flutter
- 混合渲染:小程式
jsbridge
現在很多App的頁面,不一定都是原生實現的,可能是通過webview直接載入一個線上的h5站點。比如開啟某粉紅App的會員購頁面,其實就是個移動端的網站。
這麼一說,好像和混合開發也沒啥聯絡。不過你仔細看下頁面的右上角,會發現有個分享按鈕:點選分享圖示,可以把當前頁面分享到第三方平臺,分享後,web頁面需要知道是否分享成功。
這裡就涉及了native端和web端的通訊:native分享的內容,需要web端的js進行設定(js -> native);native分享成功後,需要把訊息通知給js(natvie -> js)。為實現兩端的雙向通訊機制,就需要jsbridge
技術了。
Native通知JS
因為h5網頁是通過原生端的webview載入的,所以原生端對當前網頁擁有很高的許可權:Native端可以直接在當前webview裡執行js程式碼。
// web端
function nativeCallback(data) {
console.log('data', data);
}
複製程式碼
我們在js的執行環境裡定義了一個全域性方法nativeCallback
,native端可以直接執行nativeCallback(123)
方法,也就把資料傳給了js。
這種方案是不是有點熟悉,jsonp
就是類似的原理:只不過呼叫全域性方法的時機,從伺服器端改成了native端。
JS通知Native
前端常見的協議有:
- http/https協議:
https://www.baidu.com
- 本地file協議:
file:///Users/deepred/myproject/index.html
其實我們也可以自定義協議:sslocal://openModal?text=hello
,客戶端通過分析這段scheme
就能知道web端要呼叫原生的哪些方法,同時資料也通過query引數進行了傳遞。
那web端如何傳送這段scheme給native端呢?
- 攔截
console
alert
prompt
全域性方法。
alert('sslocal://openModal?text=hello')
複製程式碼
native可以攔截webview中的這些方法,從而呼叫原生方法。
- 攔截url請求
const ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = 'sslocal://openModal?text=hello';
document.body.appendChild(ifr);
複製程式碼
web端載入了一個iframe,請求了sslocal://openModal?text=hello
, native端通過攔截url請求,從而呼叫原生方法。
使用scheme
字串來呼叫方法始終不夠直觀,其實我們還可以向webview裡注入一個js全域性物件,這個全域性物件擁有呼叫native的方法的能力。
- API注入
// nativeApp是由native端注入的全域性變數
nativeApp.openModal('hello');
複製程式碼
雙向通訊
前面我們介紹的幾種方法,都只能單向通訊。如何進行雙向通訊呢?這時候就需要前端自己實現一個JS-SDK,維護js回撥函式的Map。
首先,我們假設客戶端會向webview中注入一個全域性物件BILIAPP
// BILIAPP是原生端注入的
const BILIAPP = {
invoke(methodName, param, onSuccessKey, onFailKey) {}
}
複製程式碼
該物件有個invoke
方法,接收4個引數:
- 呼叫的原生方法名
- 方法引數
- 成功回撥函式id
- 失敗回撥函式id
我們沒法直接傳函式給原生方法,所以這裡只能傳回撥函式的id,id對應的實際函式,由前端這邊維護。
sdk.js
let id = 1;
const uuid = () => {
return `callback_${id++}`;
};
// BILISDK是web端注入的
const BILISDK = {
// key是回撥函式的id
// value是回撥函式的值
callbacks: {
},
// 暴露給前端使用的方法,支援Promise
invokeP(methodName, param) {
return new Promise((resolve, reject) => {
const successCb = (data) => {
resolve(data);
};
const failureCb = (data) => {
reject(data);
};
return BILISDK._invoke(methodName, param, successCb, failureCb);
});
},
// 實際真正呼叫原生物件的方法
_invoke(methodName, param, successCb, failureCb) {
const onSuccessKey = uuid();
const onFailKey = uuid();
// 存入callbacks hash表中
this.callbacks[onSuccessKey] = successCb;
this.callbacks[onFailKey] = failureCb;
// BILIAPP是否注入成功
BILIAPP && BILIAPP.invoke && BILIAPP.invoke(methodName, JSON.stringify(param), onSuccessKey, onFailKey);
},
// 暴露給原生端使用的方法
invokeFromNative(key, param) {
if (typeof param === "string") {
try {
param = JSON.parse(param)
} catch (ex) {
}
}
const callback = this.callbacks[key];
if (callback) {
callback(param);
}
}
}
// 使用BILISDK呼叫原生方法
BILISDK.invokeP('getVersion').then((res) => {
console.log('res', res);
})
複製程式碼
現在前端調原生方法,不要直接使用BILIAPP.invoke
,而是通過BILISDK.invokeP
間接呼叫。BILISDK.invokeP
支援Promise化,同時維護了一個hash表
const BILISDK = {
callbacks: {
'callback_1': function() {},
'callback_2': function() {},
},
}
複製程式碼
BILISDK.invokeFromNative
是暴露給Native端使用的。當原生方法呼叫完成後,根據成功還是失敗,Native端可以呼叫BILISDK.invokeFromNative(成功或者失敗的id)
,而這個id就是當初BILIAPP.invoke
呼叫時傳進來的id。
通過上面的方法,我們就實現了js -> native -> js 的雙向通訊了。當然理論上,我們還需實現:native -> js -> native 的雙向通訊,但是原理是一樣的,這時客戶端就需要自己實現一個Native-SDK,維護Native端回撥函式的Map。
JS-SDK的接入
前面我們實現的sdk.js
,如何引入web站點呢?
把sdk打包成umd規範的js靜態檔案,上傳到cdn或者釋出到npm
- 在index.html裡面直接通過script標籤引入或者js直接
import
匯入即可。該方案,前端維護sdk。(維護成本高) - 客戶端在初始化一個WebView開啟頁面時,直接注入sdk。該方案,客戶端維護sdk。(優先推薦)