Hybrid App技術解析 -- 實戰篇

引言

上一篇原理篇,咱們已經詳細地闡述了 Hybrid App 的基礎原理,瞭解了 Native端 和 H5端 是如何通訊的,還有 bridge 的設計和接入。而本篇文章將開始把這些緣由進一步實踐,用代碼真正地去實現一套完整且穩定的 Hybrid 方案。若是對原理還有疑問的小夥伴,請移步Hybrid App技術解析 -- 原理篇,只有在理解了理論的基礎上,進一步與實踐相結合,才能真正地去深刻一項技術。前端

若是你們有什麼更好的方案或建議,能夠到 github.com/xd-tayde 上與我進行討論哈!git

摩天大樓

說了那麼一大堆理論知識,可能有小夥伴會說:「 你是否是吹流弊啊。」。😅。那就先來簡單介紹下咱們已經使用這套方案落地的項目之一。github

這是一個徹底內置在 App 裏的 Hybrid 模塊,由 Native 與 H5 深度協做完成,總共有 4個頁面,其中首頁和製做頁由 H5 製做,而相機頁和保存頁是複用Native頁面。ajax

項目上線一年累積使用次數已經超過10億次。這套方案經受住了考驗,並在過程當中仍然在不斷的優化和拓展。算法

使用這套實現方案是基於如下幾點考慮:後端

  • 整個模塊的風格多變,總體UI是與妝容所搭配的,而整個模塊一直都在持續不斷的迭代之中;
  • 項目邏輯流程的可變性大,須要H5強大的熱更新能力,及時應對數據的變化,快速的試錯和糾正
  • 拍攝頁與保存頁是客戶端已經有的模塊,能夠略微定製後直接複用
  • 須要由客戶端協助接入多套SDK,例如使用算法SDK進行復雜的圖像處理。

簡單看完項目,咱們接下來開始 bridge.js 的構建。因爲本系列文章主要面向前端童鞋,所以咱們主要展開 H5 的部分,即會注入到每一個頁面頭部的 bridge.js 的實現,客戶端中的 SDK 部分就不詳細解構了,只會提到一些細節。跨域

搭建地基 --- bridge.js 架構

基於上篇文章闡述的結構,咱們進一步去完善細節部分,先整理成下面這樣的流程結構圖,你們先看下圖,有個大體的概念:緩存

nativeCallpostMessage 這兩個主體 API 橋接了 Native端 和 H5端服務器

接下來咱們會細看裏面各個部分的代碼實現。markdown

(一) 業務方使用姿式

首先,咱們先看下在這套方案中,業務方是如何使用的,下面以獲取網絡狀態爲例:

(二) H5 --> Native

接下來直接來看 nativeCall 的內部實現:

裏面能夠解構成下面4個步驟:

  1. 生成惟一 handler 標識,從 0 開始累加;
  2. 將參數按 handler 值的規則存入參數池(_paramsStore)中;
  3. 以 handler 註冊自定義事件,綁定 callback,並將 callback也存入 _callbackStore 中,addEvent(),儲存的目的主要是爲了事件解綁時使用;
  4. 以 iframe 的形式發送協議,並攜帶惟一標識 handler,send()

Native:

  • 客戶端接收到請求後,會使用 handler 調用 getParam 從參數池中獲取對應的參數

  • 執行協議對應的功能

這樣即走通了 H5 --> Native 的這個流程,在客戶端完成了對應的功能後,既開始回傳執行結果。

(二) Native --> H5

Native:

  • Native 完成功能後,直接調用 Bridge.postMessage(handler, data),將 執行結果 和 以前 nativeCall 傳過來的 標識 回傳給 H5;

H5:

  • H5 在接收到惟一標識後初始化對應的自定義事件,掛載數據後觸發,這裏涉及的就是 fireEvent 這個函數:

這樣,咱們就已經完成了雙端之間的雙向交互機制了,梳理出了整個 bridge.js 的核心代碼了,包含了:

  • 最重要的開放API: nativeCallpostMessage

  • 客戶端獲取參數函數: getParam

  • 事件回調系統中的 addEventfireEvent

  • 用於發送協議的 send

安卓兼容性:

若是看過上一篇原理篇的童鞋,這時可能會有個疑問:在 Android 4.4如下時,使用的 loadUrl 進行 js 函數的調用,而此時是沒法獲取函數的返回值的,也就是說4.4- 時,安卓並沒有法經過 getParam 這個函數來獲取到協議的參數,這裏須要作兼容性的處理,而咱們這裏可使用一個曲線救國的騷操做,使用到的原理就是上一篇文章中有提到的另外一種 H5 -> Native 的方案:

WebView 中的 prompt 攔截

方案以下:

  • 當安卓接受到協議,並拿到 handler 值;
  • 使用無兼容性問題的 loadUrl 執行 js:Bridge.getParam(handler) ,直接將返回值直接經過 js 中的 prompt 發出:

  • 經過重寫 onJsPrompt 這個方法,攔截上一步發出的 prompt 的內容,並解析出相應的參數;

經過這樣的方式,安卓全平臺均可以完成參數的獲取,而且方式統一,不須要分平臺兼容,這就很是的skrskr啦。~~🤘🏻🤘🏻

如今看下來,是否是以爲炒雞簡單?。分分鐘能寫100個。😂。沒錯!其實核心的原理就是這麼的簡單,但這只是一個最基礎的地基而已,而基於地基之上,咱們就能夠開始一層一層建造咱們的大樓了!

建造大樓 --- 協議的定製

在完成最基礎的架構後,咱們就能夠開始來進一步完成一些上層建築了,制定一系列真正開放給業務方使用的協議 API,完善整套方案。

首先咱們能夠將這些協議分紅 功能協議業務協議

功能協議

這類協議是指用於完善整套方案的基礎功能的一些通用協議,以command://做爲通用頭,封裝在 SDK 之中,能夠在全線 App、全線 WebView 中使用:

1.初始化機制

上篇文章有提到因爲 bridge.js 注入的異步性,咱們須要由客戶端在注入完成後通知 H5。

這裏咱們能夠約定一個通用的初始化事件,這裏咱們約定爲 _init_,所以前端就能夠進行入口的監聽, 相似於咱們經常使用的 DOMContentLoaded:

你們能夠看到,這裏用了個標記位用於避免事件被重複觸發,這是因爲客戶端中是經過監聽 WebView 的生命週期鉤子來觸發的,而 iframe 之類的操做會致使這些鉤子的屢次觸發,所以須要雙方各作一層防護性措施。

接下來,咱們能夠經過該事件,直接初始化傳給H5一些環境參數和系統信息等,下面是咱們使用到的:

一樣的,咱們能夠約定更多的頁面生命週期事件,例如由於App很常常性的隱藏到後臺,所以在被激活時,咱們能夠設置個生命週期: _resume_,能夠用於告知 H5 頁面被激活。

Tips: 這裏就能體現出咱們經過事件機制來做爲回調系統的優點了,咱們能夠以最習慣的方式進行事件的監聽,而客戶端能夠直接使用 bridge.fireEvent('_init_', data)觸發事件,這樣即可以優雅地實現 Native -> H5 的單方向交互

2.包更新機制

Hybrid模塊 的其中一種方式是將前端代碼打包後內置於 App 本地,以便擁有最快的啓動性能和離線訪問能力。而這種方式最大的麻煩點,就是代碼的更新,咱們不可能每次有修改時就手動從新打包給客戶端童鞋替換,並且這樣也失去了咱們的熱更新機制。

所以這裏就須要一套新的熱更新機制,這套機制須要由客戶端/前端/服務端 三端的童鞋提供對應的資源,共同協做完成整套流程。

資源:

  • H5: 每一個代碼包都有一個惟一且遞增的版本號
  • Native: 提供包下載且解壓到對應目錄的服務,前端能夠由下面這個協議來調用該功能。

  • 服務端: 提供一個接口,能夠獲取線上最新代碼包的版本號和下載地址

流程:

  • 前端更新代碼打包後按版本號上傳至指定的服務器上
  • 每次打開頁面時,H5請求接口獲取線上最新代碼包版本號,並與本地包進行版本號比對,當線上的版本號 大於 本地包版本號時,發起包下載協議
  • 客戶端接受到協議後,直接去線上地址下載最新的代碼包,並解壓替換到當前目錄文件

擁有這樣的機制後,H5在開發後,就能夠直接打包將包上傳到對應的服務器上,這樣在 App 中打開頁面後,便可以實時的熱更新。

3.環境系統 和 多語言系統

一般,咱們會將項目分紅多個不一樣的環境,相互隔離。而因爲 Hybrid 模塊是置於 App 中的,所以環境須要與 App 進行匹配,這裏就能夠直接使用上面第一點提到的,經過 _init_ 中攜帶的數據data.env來匹配:

env: 0 - 正式環境; 1 - 測試環境; 2 - 開發環境;

同理, 多語言也能夠直接使用 e.data.language 直接進行匹配;

Tips:

環境機制咱們一般主要用於匹配後端的環境,正式環境和測試環境對應不一樣的接口。而這裏還有一點特別的,就是須要注意代碼包的更新,上述的包更新條件要包含三個方面: 版本號、環境和 App版本,在不一樣環境不一樣 App 版本下,也應該更新到相應的最新代碼包。

4. 事件中轉站

因爲頁面是 H5 開發,而 Native 可能須要控制 H5 頁面,例如最經常使用的場景:

當頁面中有彈窗或者SPA切換頁面時,安卓的返回實體鍵應該能完成對應的回退,而不是由於 WebView 沒有 history 就直接關閉。

相似於這類需求,這裏就能夠定製一個事件中心(_eventListeners_),用於監聽客戶端的實體返回鍵:

5. 數據傳遞機制

在業務中,不少場景須要作到 Native 與 H5 保持數據的同步,此時就可使用相似上面的原理,制定一套數據傳遞協議:

Tips:

Hybrid模塊一般須要從對應的入口進入,所以這裏有一種能夠優化的方式:

由 App 在啓動時先去獲取線上數據,在進入 WebView 後直接經過 _init_ 或者觸發 getData 直接發送給 H5,這樣能減小請求數量,優化用戶體驗。

6. 代理請求

H5中最經常使用的就是請求,一般咱們能夠直接使用ajax,可是這裏有幾個問題比較棘手:

  • 最多見的請求跨域
  • 數據算法加密
  • 用戶登陸校驗

而客戶端的請求便不會出現這些問題,所以咱們能夠由客戶端代理咱們發出的請求,能夠定製4個協議: getProxypostProxygetProxyLoginedpostProxyLogined,其中帶有 Logined 的協議表明着在請求時會自動攜帶已登陸用戶的 token 和 uid 等參數,使用在一些須要登陸信息的接口上。這樣作的好處是

  • H5 方就無需處理繁多的各項複雜信息,不須要進行跨端傳輸;
  • 可以對 H5 與 Native 的請求出口進行統一,方便加工處理。

7.更多

除了這些重要的功能外,咱們還能夠很是自由地定製不少協議,讓 H5 擁有更多更強大的功能,下面是咱們所定製的一些功能:

  • getNetwork:獲取網絡狀態;
  • openApp:喚起其它 App;
  • setShareInfocallShare:分享內容到第三方平臺;
  • link:使用新的 WebView 打開頁面;
  • closeWebview:關閉 WebView;
  • setStoragegetStorage:設置與獲取緩存數據;
  • loading:調用客戶端通用 Loading;
  • setWebviewTitle:設置 WebView 標題;
  • saveImage:保存圖片到本地;
  • ...

這裏能夠定義更多的通用性協議,這裏有個原則能夠遵照,即這部分協議應該是基礎性功能,應該是純淨的,適用於全部的業務方。根據上篇文章提到的理念,這部分是當成通用 SDK 進行維護與升級的,所以不該該耦合業務層的任何邏輯。

而有時咱們會遇到須要定製一些業務上的邏輯,例如上面提到的項目中,咱們要將用戶圖片經過算法處理成卡通畫。這樣的需求就是很是的業務化,不適用於其它項目,所以咱們應該定製成業務協議。

業務協議

這類協議區別於功能協議,它們會雜合必定程度的業務邏輯,而這些邏輯只是針對於特定的項目。其實對於 H5 的使用上,差異並不大,只是使用對應特殊的協議頭用於區分,例如:

這類協議一般不包含在 SDK 中,所以須要由客戶端的童鞋針對項目的 WebView 進行定製,使用 bridge.js 提供的基礎功能實現對應的複雜功能。而在其它的項目入口中,就沒法使用這些協議。

總結

看到總結兩個字,有沒有長舒了一口氣。。😅。經過這兩篇文章,咱們終於將 Hybrid 方案的前端部分徹底的解構清楚了,是否是有種神清氣爽的感受,徹底能夠立刻開啓大家的 Hybrid 之旅了。鼓掌鼓掌!!👏👏👏!!

但這也遠非終點,或者說這永無終點。~大樓建成後,離真正的摩天大樓仍是差着一步 --- 內部裝修,其實接下來咱們還須要作不少的優化措施,來解決一些仍然存在的問題,這部分其實咱們也一直還在努力的階段。

受篇幅所限,有時間會將這部分再寫一篇優化篇,主要來與你們探討下咱們所能想到的一些優化方案,很是期待大佬們也能給咱們提供更多的建議和解決辦法。感恩~~😇

更多文章 摸我 或者關注公衆號閱讀。。😻😻😻