Hybrid app本地開發如何調用JSBridge

前天同事問我公司內部的小程式怎麼對接的,我回憶了一下,簡單記錄了一下前端同學需要注意的點。

背後還有小程式架構、網路策略等等。當時恰逢小程式架構調整,(老架構的時候我就發現了有一個問題點可以優化,但是跟那邊人回饋之後,人家表示不要我管😂,新架構時發現這個問題還巧妙的遺留下來了😮)我雖然不負責那塊,但是本著這樣不優雅的原則,還是跟新架構的對接人講了我的優化方案,講明白了之後,同時上報各自直系領導,並建議我領導牽頭開會推動。最後,無奈存量數據太多,老架構那邊權衡之後決定不改動。(多說了幾句,權當記錄一下)

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方法自動替換掉我模擬實現的方法。

於是我就開始了下面的準備工作。

  1. 搞清楚JSBridge運行的原理

  2. 本地模擬JSBridge的方法

  3. 上架小程式是自動使用真實的JSBridge

3、了解JSBridge

JSBridge:望文生義就是jsNative之前的橋樑,而實際上JSBridge確實是JSNative之前的一種通訊方式

簡單的說,JSBridge就是定義NativeJS的通訊,Native只通過一個固定的橋對象調用JSJS也只通過固定的橋對象調用NativeJSBridge另一個叫法及大家熟知的Hybrid app技術。

了解即可,更多的請參考

下圖展示了JSBridge的工作流程👇

上圖中左側部分正式我要做的,具體請看下文

看累了,三連一下,回看不迷路喲😉

3.1、我們的JSBridge

推測「大地」那邊的JSBridge應該是自己寫的,沒有初始化JSBridge的操作

當調用JSBridge時,必須在頁面完全載入完成之後才能夠拿到全局的JSBridgeCordova框架提供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,文檔看這裡

  1. 用法
new CustomEvent(eventName, params);
  1. 示例

創建一個自定義事件

const event=new CustomEvent('mock-event'); 
  1. 傳遞參數

這裡值得注意,需要把想要傳遞的參數包裹在一個包含detail屬性的對象,否則傳遞的參數不會被掛載

function createEvent(params, eventName = 'mock-event') {
    return new CustomEvent(eventName, { detail: params });
}

const event = createEvent({ id: '0010' });
  1. 發起事件

調用dispatchEvent方法發起事件,傳入你剛才創建的方法

window.dispatchEvent(event); 
  1. 監聽事件
window.addEventListener('mock-event', ({ detail: { id } }) => {
    console.log('id',id) // 會在控制台列印0010
});
  1. 示例:
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事件

  1. 自定義deviceready事件

  2. 根據上面測試執行順序得出的結論,我在load事件里觸發自定義事件

  3. 在開發環境下模擬一些用到的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 的實現,在這種場景下其實是不合理的👀
只是形式上類似,其實並不是

  1. 定義
  • mixin.js
deviceReady() {
  return new Promise((resolve) => {
    window.addEventListener('deviceready', function () {
      resolve("ready go!");
    }, false);
  })
}
  1. 組件內使用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);
  });
})
  1. 開發環境執行效果如下

7.2、方式二(推薦)

改寫成通用的事件監聽函數,支援鏈式調用

  • 開發環境下,由mockEvent.js文件里的dispatchEvent觸發自定義的deviceready事件;

  • 小程式里運行,則由真實的deviceready事件觸發

  1. 定義
  • 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;
}
  1. 使用
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

熱愛前端,也喜歡專研各種跟本職工作關係不大的技術,技術、產品興趣廣泛且濃厚,等待著一個創業機會。主要致力於分享實用技術乾貨,希望可以給一小部分人一些微小幫助。

我排斥「新人迷茫,老人看戲」的現象,希望能和大家一起努力破局。營造一個良好的技術氛圍,為了個人、為了中國的數字化轉型、互聯網物聯網技術、數字經濟發展做一點點貢獻。數風流人物還看中國、看今朝、看你我