上一篇原理篇,咱們已經詳細地闡述了 Hybrid App 的基礎原理,瞭解了 Native端 和 H5端 是如何通訊的,還有 bridge 的設計和接入。而本篇文章將開始把這些緣由進一步實踐,用代碼真正地去實現一套完整且穩定的 Hybrid 方案。若是對原理還有疑問的小夥伴,請移步Hybrid App技術解析 -- 原理篇,只有在理解了理論的基礎上,進一步與實踐相結合,才能真正地去深刻一項技術。前端
若是你們有什麼更好的方案或建議,能夠到 github.com/xd-tayde 上與我進行討論哈!git
說了那麼一大堆理論知識,可能有小夥伴會說:「 你是否是吹流弊啊。」。😅。那就先來簡單介紹下咱們已經使用這套方案落地的項目之一。github
這是一個徹底內置在 App 裏的 Hybrid 模塊,由 Native 與 H5 深度協做完成,總共有 4個頁面,其中首頁和製做頁由 H5 製做,而相機頁和保存頁是複用Native頁面。ajax
項目上線一年累積使用次數已經超過10億次。這套方案經受住了考驗,並在過程當中仍然在不斷的優化和拓展。算法
使用這套實現方案是基於如下幾點考慮:後端
簡單看完項目,咱們接下來開始 bridge.js 的構建。因爲本系列文章主要面向前端童鞋,所以咱們主要展開 H5 的部分,即會注入到每一個頁面頭部的 bridge.js 的實現,客戶端中的 SDK 部分就不詳細解構了,只會提到一些細節。跨域
基於上篇文章闡述的結構,咱們進一步去完善細節部分,先整理成下面這樣的流程結構圖,你們先看下圖,有個大體的概念:緩存
nativeCall
與 postMessage
這兩個主體 API 橋接了 Native端 和 H5端服務器
接下來咱們會細看裏面各個部分的代碼實現。markdown
首先,咱們先看下在這套方案中,業務方是如何使用的,下面以獲取網絡狀態爲例:
接下來直接來看 nativeCall
的內部實現:
裏面能夠解構成下面4個步驟:
addEvent()
,儲存的目的主要是爲了事件解綁時使用;send()
;Native:
getParam
從參數池中獲取對應的參數。這樣即走通了 H5 --> Native 的這個流程,在客戶端完成了對應的功能後,既開始回傳執行結果。
Native:
Bridge.postMessage(handler, data)
,將 執行結果 和 以前 nativeCall
傳過來的 標識 回傳給 H5;H5:
fireEvent
這個函數:這樣,咱們就已經完成了雙端之間的雙向交互機制了,梳理出了整個 bridge.js 的核心代碼了,包含了:
最重要的開放API: nativeCall
與 postMessage
;
客戶端獲取參數函數: getParam
;
事件回調系統中的 addEvent
和 fireEvent
;
用於發送協議的 send
。
若是看過上一篇原理篇的童鞋,這時可能會有個疑問:在 Android 4.4如下時,使用的 loadUrl
進行 js 函數的調用,而此時是沒法獲取函數的返回值的,也就是說4.4- 時,安卓並沒有法經過 getParam
這個函數來獲取到協議的參數,這裏須要作兼容性的處理,而咱們這裏可使用一個曲線救國的騷操做,使用到的原理就是上一篇文章中有提到的另外一種 H5 -> Native 的方案:
WebView 中的 prompt 攔截
方案以下:
loadUrl
執行 js:Bridge.getParam(handler)
,直接將返回值直接經過 js 中的 prompt
發出:onJsPrompt
這個方法,攔截上一步發出的 prompt 的內容,並解析出相應的參數;經過這樣的方式,安卓全平臺均可以完成參數的獲取,而且方式統一,不須要分平臺兼容,這就很是的skrskr啦。~~🤘🏻🤘🏻
如今看下來,是否是以爲炒雞簡單?。分分鐘能寫100個。😂。沒錯!其實核心的原理就是這麼的簡單,但這只是一個最基礎的地基而已,而基於地基之上,咱們就能夠開始一層一層建造咱們的大樓了!
在完成最基礎的架構後,咱們就能夠開始來進一步完成一些上層建築了,制定一系列真正開放給業務方使用的協議 API,完善整套方案。
首先咱們能夠將這些協議分紅 功能協議 和 業務協議。
這類協議是指用於完善整套方案的基礎功能的一些通用協議,以command://
做爲通用頭,封裝在 SDK 之中,能夠在全線 App、全線 WebView 中使用:
上篇文章有提到因爲 bridge.js 注入的異步性,咱們須要由客戶端在注入完成後通知 H5。
這裏咱們能夠約定一個通用的初始化事件,這裏咱們約定爲 _init_
,所以前端就能夠進行入口的監聽, 相似於咱們經常使用的 DOMContentLoaded
:
你們能夠看到,這裏用了個標記位用於避免事件被重複觸發,這是因爲客戶端中是經過監聽 WebView 的生命週期鉤子來觸發的,而 iframe 之類的操做會致使這些鉤子的屢次觸發,所以須要雙方各作一層防護性措施。
接下來,咱們能夠經過該事件,直接初始化傳給H5一些環境參數和系統信息等,下面是咱們使用到的:
一樣的,咱們能夠約定更多的頁面生命週期事件,例如由於App很常常性的隱藏到後臺,所以在被激活時,咱們能夠設置個生命週期: _resume_
,能夠用於告知 H5 頁面被激活。
Tips: 這裏就能體現出咱們經過事件機制來做爲回調系統的優點了,咱們能夠以最習慣的方式進行事件的監聽,而客戶端能夠直接使用 bridge.fireEvent('_init_', data)
觸發事件,這樣即可以優雅地實現 Native -> H5 的單方向交互。
Hybrid模塊 的其中一種方式是將前端代碼打包後內置於 App 本地,以便擁有最快的啓動性能和離線訪問能力。而這種方式最大的麻煩點,就是代碼的更新,咱們不可能每次有修改時就手動從新打包給客戶端童鞋替換,並且這樣也失去了咱們的熱更新機制。
所以這裏就須要一套新的熱更新機制,這套機制須要由客戶端/前端/服務端 三端的童鞋提供對應的資源,共同協做完成整套流程。
資源:
流程:
擁有這樣的機制後,H5在開發後,就能夠直接打包將包上傳到對應的服務器上,這樣在 App 中打開頁面後,便可以實時的熱更新。
一般,咱們會將項目分紅多個不一樣的環境,相互隔離。而因爲 Hybrid 模塊是置於 App 中的,所以環境須要與 App 進行匹配,這裏就能夠直接使用上面第一點提到的,經過 _init_
中攜帶的數據data.env
來匹配:
env: 0 - 正式環境; 1 - 測試環境; 2 - 開發環境;
同理, 多語言也能夠直接使用 e.data.language
直接進行匹配;
Tips:
環境機制咱們一般主要用於匹配後端的環境,正式環境和測試環境對應不一樣的接口。而這裏還有一點特別的,就是須要注意代碼包的更新,上述的包更新條件要包含三個方面: 版本號、環境和 App版本,在不一樣環境不一樣 App 版本下,也應該更新到相應的最新代碼包。
因爲頁面是 H5 開發,而 Native 可能須要控制 H5 頁面,例如最經常使用的場景:
當頁面中有彈窗或者SPA切換頁面時,安卓的返回實體鍵應該能完成對應的回退,而不是由於 WebView 沒有 history 就直接關閉。
相似於這類需求,這裏就能夠定製一個事件中心(_eventListeners_
),用於監聽客戶端的實體返回鍵:
在業務中,不少場景須要作到 Native 與 H5 保持數據的同步,此時就可使用相似上面的原理,制定一套數據傳遞協議:
Tips:
Hybrid模塊一般須要從對應的入口進入,所以這裏有一種能夠優化的方式:
由 App 在啓動時先去獲取線上數據,在進入 WebView 後直接經過 _init_
或者觸發 getData
直接發送給 H5,這樣能減小請求數量,優化用戶體驗。
H5中最經常使用的就是請求,一般咱們能夠直接使用ajax,可是這裏有幾個問題比較棘手:
而客戶端的請求便不會出現這些問題,所以咱們能夠由客戶端代理咱們發出的請求,能夠定製4個協議: getProxy
,postProxy
, getProxyLogined
,postProxyLogined
,其中帶有 Logined
的協議表明着在請求時會自動攜帶已登陸用戶的 token 和 uid 等參數,使用在一些須要登陸信息的接口上。這樣作的好處是
除了這些重要的功能外,咱們還能夠很是自由地定製不少協議,讓 H5 擁有更多更強大的功能,下面是咱們所定製的一些功能:
getNetwork
:獲取網絡狀態;openApp
:喚起其它 App;setShareInfo
與callShare
:分享內容到第三方平臺;link
:使用新的 WebView 打開頁面;closeWebview
:關閉 WebView;setStorage
與 getStorage
:設置與獲取緩存數據;loading
:調用客戶端通用 Loading;setWebviewTitle
:設置 WebView 標題;saveImage
:保存圖片到本地;這裏能夠定義更多的通用性協議,這裏有個原則能夠遵照,即這部分協議應該是基礎性功能,應該是純淨的,適用於全部的業務方。根據上篇文章提到的理念,這部分是當成通用 SDK 進行維護與升級的,所以不該該耦合業務層的任何邏輯。
而有時咱們會遇到須要定製一些業務上的邏輯,例如上面提到的項目中,咱們要將用戶圖片經過算法處理成卡通畫。這樣的需求就是很是的業務化,不適用於其它項目,所以咱們應該定製成業務協議。
這類協議區別於功能協議,它們會雜合必定程度的業務邏輯,而這些邏輯只是針對於特定的項目。其實對於 H5 的使用上,差異並不大,只是使用對應特殊的協議頭用於區分,例如:
這類協議一般不包含在 SDK 中,所以須要由客戶端的童鞋針對項目的 WebView 進行定製,使用 bridge.js 提供的基礎功能實現對應的複雜功能。而在其它的項目入口中,就沒法使用這些協議。
看到總結兩個字,有沒有長舒了一口氣。。😅。經過這兩篇文章,咱們終於將 Hybrid 方案的前端部分徹底的解構清楚了,是否是有種神清氣爽的感受,徹底能夠立刻開啓大家的 Hybrid 之旅了。鼓掌鼓掌!!👏👏👏!!
但這也遠非終點,或者說這永無終點。~大樓建成後,離真正的摩天大樓仍是差着一步 --- 內部裝修,其實接下來咱們還須要作不少的優化措施,來解決一些仍然存在的問題,這部分其實咱們也一直還在努力的階段。
受篇幅所限,有時間會將這部分再寫一篇優化篇,主要來與你們探討下咱們所能想到的一些優化方案,很是期待大佬們也能給咱們提供更多的建議和解決辦法。感恩~~😇
更多文章 摸我 或者關注公衆號閱讀。。😻😻😻