Hybrid app本地開發如何調用JSBridge
- 2022 年 9 月 8 日
- 筆記
- Hybrid App, 前端
前天同事問我公司內部的小程式怎麼對接的,我回憶了一下,簡單記錄了一下前端同學需要注意的點。
背後還有小程式架構、網路策略等等。當時恰逢小程式架構調整,(老架構的時候我就發現了有一個問題點可以優化,但是跟那邊人回饋之後,人家表示不要我管😂,新架構時發現這個問題還巧妙的遺留下來了😮)我雖然不負責那塊,但是本著這樣不優雅的原則,還是跟新架構的對接人講了我的優化方案,講明白了之後,同時上報各自直系領導,並建議我領導牽頭開會推動。最後,無奈存量數據太多,老架構那邊權衡之後決定不改動。(多說了幾句,權當記錄一下)
1、背景
公司研發的一款服務軟體App(姑且稱為「大地」),提供了包涵消息、待辦、工作台、同事圈和通訊錄五大功能模組,其中,工作台里集成了包括公司的移動客戶端、PC端以及第三方平台的部分功能/服務(統稱為「應用」)。
我今天要講的是這個集成平台以什麼方式展現「應用」,答案是:借鑒了微信的架構,自研了「小程式」接入「應用」。
我司小程式具有一種相對開放能力(面向全公司),賦能業務快速數字化、場景敏捷迭代,並且可在「大地」上便捷的獲取和使用,同時具有完善的使用體驗(這就是嚴格的接入審核標準帶來的好處)。
在「大地」開發者平台,創建小程式會自動創建配套的公眾號(公眾號是為了推送消息使用,可訂閱)。小程式開發不限制技術選型,開發完成之後按照小程式接入規範打包上架小程式,審核發布。
簡單來說,可以把「大地」看成是一個「釘釘」,我現在要把我們的業務功能投放到「大地」上,就需要接入「大地」小程式,以小程式的方式在「大地」上為用戶提供服務。
小程式架構:Cordova框架做的WebView,運行我開發的前端程式,通過Nginx幫我把請求代理到微服務網關,由網關轉發到目的主機處理請求。它雖然看上去是一個Native App,但只有一個UI WebView,裡面訪問的是一個Web App,對我來說就是開發一個H5應用調用一些所需的JSBridge,也就是所謂的Hybrid App。
下面看一下本地開發中的一些問題,以及我是怎麼處理的
2、問題
Hybrid App本地開發過程中沒有真實的Native環境的,同樣也無法使用JSBridge,這就會帶來一個問題:跟原生交互的行為只能發布小程式才可以調試,本地玩不了,這…,相當fuck。
目的是想讓本地開發同小程式測試環境具有相同的體驗,我的想法是在本地模擬JSBridge的方法,儘管不能帶來真實的效果,至少觸發了某個行為之後要有個反應,不至於讓操作流程看起來像是「脫節」的(實際跟原生的交互行為並不多,比如:拍照、彈窗提示、定位等等)。
因此,我要做的就是本地模擬JSBridge的一些方法,開發時觸發了這些原生交互行為之後提示一些資訊,等到上架小程式測試環境時,在手機上會用真實的JSBridge方法自動替換掉我模擬實現的方法。
於是我就開始了下面的準備工作。
-
搞清楚
JSBridge運行的原理 -
本地模擬
JSBridge的方法 -
上架小程式是自動使用真實的
JSBridge
3、了解JSBridge
JSBridge:望文生義就是js和Native之前的橋樑,而實際上JSBridge確實是JS和Native之前的一種通訊方式。
簡單的說,JSBridge就是定義Native和JS的通訊,Native只通過一個固定的橋對象調用JS,JS也只通過固定的橋對象調用Native。JSBridge另一個叫法及大家熟知的Hybrid app技術。

了解即可,更多的請參考
下圖展示了JSBridge的工作流程👇

上圖中左側部分正式我要做的,具體請看下文
看累了,三連一下,回看不迷路喲😉
3.1、我們的JSBridge
推測「大地」那邊的JSBridge應該是自己寫的,沒有初始化JSBridge的操作
當調用JSBridge時,必須在頁面完全載入完成之後才能夠拿到全局的JSBridge,Cordova框架提供deviceready事件,該事件觸發的時候表示全局的JSBridge掛載成功。(注意:這就是我接下來操作的切入點,嘻嘻)
簡單寫下如下:
document.addEventListener('deviceready', function () {
console.log('deviceready OK!');
JSAPI.showToast(0, '提示資訊')
}, false)
需要注意的是,在開發環境,是沒有 deviceready 事件的,所以上面的程式碼並不會執行,只有在app裡面運行的時候才會執行。
思考:
JSBridge必須是在deviceready事件觸發後方能使用的,因此首先要做的就是自定義deviceready事件,本地環境可以在load事件里觸發自定義deviceready事件,生產環境下監聽deviceready事件即可

4、JS發起自定義事件
我是用 CustomEvent 構造函數,繼承至 Event,文檔看這裡
- 用法
new CustomEvent(eventName, params);
- 示例
創建一個自定義事件
const event=new CustomEvent('mock-event');
- 傳遞參數
這裡值得注意,需要把想要傳遞的參數包裹在一個包含detail屬性的對象,否則傳遞的參數不會被掛載
function createEvent(params, eventName = 'mock-event') {
return new CustomEvent(eventName, { detail: params });
}
const event = createEvent({ id: '0010' });
- 發起事件
調用dispatchEvent方法發起事件,傳入你剛才創建的方法
window.dispatchEvent(event);
- 監聽事件
window.addEventListener('mock-event', ({ detail: { id } }) => {
console.log('id',id) // 會在控制台列印0010
});
- 示例:
document.body.addEventListener('show', (event) => { console.log(event.detail); });
// 觸發
let myEvent = new CustomEvent('show', {
detail: {
username: 'xixi',
userid: '2022'
}
});
document.body.dispatchEvent(myEvent);
了解了自定義事件之後,通過自定義事件模擬觸發
deviceready事件,這樣上面的deviceready事件監聽就可以執行了。注意:這裡還要確定一個問題,在什麼時候觸發自定義事件
deviceready呢?
5、確定 deviceready 事件執行時機
- 只需要編寫如下程式碼,查看輸出結果即可
window.addEventListener('load', function () {
console.log('load OK!');
}, false);
document.addEventListener('deviceready', function () {
console.log('deviceready OK!');
}, false);
- 結果輸出
load OK!
deviceready OK!
由此可知,執行順序:load –> deviceready
6、自定義事件模擬Cordova deviceready事件
-
自定義
deviceready事件 -
根據上面測試執行順序得出的結論,我在
load事件里觸發自定義事件 -
在開發環境下模擬一些用到的
JSBridge-API,比如下面寫到的JSAPI.showToast()方法
- mockEvent.js
if (process.env.NODE_ENV === 'development') {
// 自定義事件
let myEvent = new CustomEvent('deviceready');
// 模擬JSAPI事件
window['JSAPI'] = {
showToast(type, desc) {
console.log(type, desc);
}
}
// ...
// 開發環境下,在 原生 load 方法之後 觸發自定義事件
window.addEventListener('load', function () {
console.log('load OK!');
setTimeout(() => {
document.body.dispatchEvent(myEvent);
}, 100)
}, false);
}
7、封裝deviceReady方法
實現在Cordova框架觸發deviceready事件的時候感知到,以便於在deviceReady事件觸發後執行JS-API。
可用於開發環境和非開發環境
7.1、方式一
這裡採用鏈式調用的方式,
以下這種藉助 Promise 的實現,在這種場景下其實是不合理的👀
只是形式上類似,其實並不是
- 定義
- mixin.js
deviceReady() {
return new Promise((resolve) => {
window.addEventListener('deviceready', function () {
resolve("ready go!");
}, false);
})
}
- 組件內使用
JS-API
使用JSAPI可以如下這麼寫
this.deviceReady().then((res) => {
console.log(res); // ready go!
JSAPI.showToast(0, '提示')
})
this.deviceReady().then((res) => {
JSAPI.getUserInfo((res) => {
console.log(res);
}, (err) => {
console.log(err);
});
})
- 開發環境執行效果如下

7.2、方式二(推薦)
改寫成通用的事件監聽函數,支援鏈式調用
開發環境下,由
mockEvent.js文件里的dispatchEvent觸發自定義的deviceready事件;小程式里運行,則由真實的
deviceready事件觸發
- 定義
- mixin.js
receiver(type) {
let callbacks = {
fns: [],
then: function(cb){
this.fns.push(cb);
return this;
}
};
document.addEventListener(type, function(ev) {
let fns = callbacks.fns.slice();
for(let i = 0, l = fns.length; i < l; i++){
fns[i].call(this, ev);
}
});
return callbacks;
}
- 使用
this.receiver('deviceready').then((ev) => {
console.log(ev);
JSAPI.getUserInfo(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
)
})
this.receiver("click")
.then(() => console.log("hi"))
.then(()=> console.log(22));
最後
當應用發布到app上,就是監聽的真實的 Cordova框架的 deviceready 事件了,之後也就可以拿到真實的JSAPI了,以上只是為了在開發環境的時候模擬使用JSAPI。防止在開發環境下直接調用JSAPI飄紅的情況,當然也是可以加try catch處理的,只不過個人感覺模擬事件使得程式碼看起來更加優雅別緻一點,使用更加絲滑,酌情食用😁。
軟體架構非常有意思,感興趣的可以交流探索,嘻嘻。

我是 甜點cc
熱愛前端,也喜歡專研各種跟本職工作關係不大的技術,技術、產品興趣廣泛且濃厚,等待著一個創業機會。主要致力於分享實用技術乾貨,希望可以給一小部分人一些微小幫助。
我排斥「新人迷茫,老人看戲」的現象,希望能和大家一起努力破局。營造一個良好的技術氛圍,為了個人、為了中國的數字化轉型、互聯網物聯網技術、數字經濟發展做一點點貢獻。數風流人物還看中國、看今朝、看你我。

