摘要:本文將從以下幾個方面闡述架構(gòu)設(shè)計的一些經(jīng)驗和思考。原文及討論請到通訊作為一種跨語言開發(fā)模式,通訊層是架構(gòu)首先應(yīng)該考慮和設(shè)計的,往后所有的邏輯都是基于通訊層展開。
關(guān)于Hybrid模式開發(fā)app的好處,網(wǎng)絡(luò)上已有很多文章闡述了,這里不展開。
本文將從以下幾個方面闡述Hybrid app架構(gòu)設(shè)計的一些經(jīng)驗和思考。
原文及討論請到 github issue
通訊作為一種跨語言開發(fā)模式,通訊層是Hybrid架構(gòu)首先應(yīng)該考慮和設(shè)計的,往后所有的邏輯都是基于通訊層展開。
Native(以Android為例)和H5通訊,基本原理:
Android調(diào)用H5:通過webview類的loadUrl方法可以直接執(zhí)行js代碼,類似瀏覽器地址欄輸入一段js一樣的效果
webview.loadUrl("javascript: alert("hello world")");
H5調(diào)用Android:webview可以攔截H5發(fā)起的任意url請求,webview通過約定的規(guī)則對攔截到的url進(jìn)行處理(消費(fèi)),即可實現(xiàn)H5調(diào)用Android
var ifm = document.createElement("iframe"); ifm.src = "jsbridge://namespace.method?[...args]";
JSBridge即我們通常說的橋協(xié)議,基本的通訊原理很簡單,接下來就是橋協(xié)議具體實現(xiàn)。
P.S:注冊私有協(xié)議的做法很常見,我們經(jīng)常遇到的在網(wǎng)頁里拉起一個系統(tǒng)app就是采用私有協(xié)議實現(xiàn)的。app在安裝完成之后會注冊私有協(xié)議到OS,瀏覽器發(fā)現(xiàn)自身不能識別的協(xié)議(http、https、file等)時,會將鏈接拋給OS,OS會尋找可識別此協(xié)議的app并用該app處理鏈接。比如在網(wǎng)頁里以itunes://開頭的鏈接是Apple Store的私有協(xié)議,點擊后可以啟動Apple Store并且跳轉(zhuǎn)到相應(yīng)的界面。國內(nèi)軟件開發(fā)商也經(jīng)常這么做,比如支付寶的私有協(xié)議alipay://,騰訊的tencent://等等。
橋協(xié)議的具體實現(xiàn)由于JavaScript語言自身的特殊性(單進(jìn)程),為了不阻塞主進(jìn)程并且保證H5調(diào)用的有序性,與Native通訊時對于需要獲取結(jié)果的接口(GET類),采用類似于JSONP的設(shè)計理念:
類比HTTP的request和response對象,調(diào)用方會將調(diào)用的api、參數(shù)、以及請求簽名(由調(diào)用方生成)帶上傳給被調(diào)用方,被調(diào)用方處理完之后會吧結(jié)果以及請求簽名回傳調(diào)用方,調(diào)用方再根據(jù)請求簽名找到本次請求對應(yīng)的回調(diào)函數(shù)并執(zhí)行,至此完成了一次通訊閉環(huán)。
H5調(diào)用Native(以Android為例)示意圖:
Native(以Android為例)調(diào)用H5示意圖:
基于橋協(xié)議的api設(shè)計(HybridApi)jsbridge作為一種通用私有協(xié)議,一般會在團(tuán)隊級或者公司級產(chǎn)品進(jìn)行共享,所以需要和業(yè)務(wù)層進(jìn)行解耦,將jsbridge的內(nèi)部細(xì)節(jié)進(jìn)行封裝,對外暴露平臺級的API。
以下是筆者剝離公司業(yè)務(wù)代碼后抽象出的一份HybridApi js部分的實現(xiàn),項目地址:
hybrid-js
另外,對于Native提供的各種接口,也可以簡單封裝下,使之更貼近前端工程師的使用習(xí)慣:
// /lib/jsbridge/core.js function assignAPI(name, callback) { var names = name.split(/./); var ns = names.shift(); var fnName = names.pop(); var root = createNamespace(JSBridge[ns], names); if(fnName) root[fnName] = callback || function() {}; }
增加api:
// /lib/jsbridge/api.js var assign = require("./core.js").assignAPI; ... assign("util.compassImage", function(path, callback, quality, width, height) { JSBridge.invokeApp("os.getInfo", { path: path, quality: quality || 80, width: width || "auto", height: height || "auto", callback: callback }); });
H5上層應(yīng)用調(diào)用:
// h5/music/index.js JSBridge.util.compassImage("http://cdn.foo.com/images/bar.png", function(r) { console.log(r.value); // => base64 data });界面與交互(Native與H5職責(zé)劃分)
本質(zhì)上,Native和H5都能完成界面開發(fā)。幾乎所有hybrid的開發(fā)模式都會碰到同樣的一個問題:哪些由Native負(fù)責(zé)哪些由H5負(fù)責(zé)?
這個回到原始的問題上來:我們?yōu)槭裁匆捎胔ybrid模式開發(fā)?簡而言之就是同時利用H5的跨平臺、快速迭代能力以及Native的流暢性、系統(tǒng)API調(diào)用能力。
根據(jù)這個原則,為了充分利用二者的優(yōu)勢,應(yīng)該盡可能地將app內(nèi)容使用H5來呈現(xiàn),而對于js語言本身的缺陷,應(yīng)該使用Native語言來彌補(bǔ),如轉(zhuǎn)場動畫、多線程作業(yè)(密集型任務(wù))、IO性能等。即總的原則是H5提供內(nèi)容,Native提供容器,在有可能的條件下對Android原生webview進(jìn)行優(yōu)化和改造(參考阿里Hybrid容器的JSM),提升H5的渲染效率。
但是,在實際的項目中,將整個app所有界面都使用H5來開發(fā)也有不妥之處,根據(jù)經(jīng)驗,以下情形還是使用Native界面為好:
關(guān)鍵界面、交互性強(qiáng)的的界面使用Native因H5比較容易被惡意攻擊,對于安全性要求比較高的界面,如注冊界面、登陸、支付等界面,會采用Native來取代H5開發(fā),保證數(shù)據(jù)的安全性,這些頁面通常UI變更的頻率也不高。
對于這些界面,降級的方案也有,就是HTTPS。但是想說的是在國內(nèi)的若網(wǎng)絡(luò)環(huán)境下,HTTPS的體驗實在是不咋地(主要是慢),而且只能走現(xiàn)網(wǎng)不能走離線通道。
另外,H5本身的動畫開發(fā)成本比較高,在低端機(jī)器上可能有些繞不過的性能坎,原生js對于手勢的支持也比較弱,因此對于這些類型的界面,可以選擇使用Native來實現(xiàn),這也是Native本身的優(yōu)勢不是。比如要實現(xiàn)下面這個音樂播放界面,用H5開發(fā)門檻不小吧,留意下中間的波浪線背景,手指左右滑動可以切換動畫。
導(dǎo)航組件采用Native導(dǎo)航組件,就是頁面的頭組件,左上角一般都是一個back鍵,中間一般都是界面的標(biāo)題,右邊的話有時是一個隱藏的懸浮菜單觸發(fā)按鈕有時則什么也沒有。
移動端有一個特性就是界面下拉有個回彈效果,頭不動body部分跟著滑動,這種效果H5比較難實現(xiàn)。
再者,也是最重要的一點,如果整個界面都是H5的,在H5加載過程中界面將是白屏,在弱網(wǎng)絡(luò)下用戶可能會很疑惑。
所以基于這兩點,打開的界面都是Native的導(dǎo)航組件+webview來組成,這樣即使H5加載失敗或者太慢用戶可以選擇直接關(guān)閉。
在API層面,會相應(yīng)的有一個接口來實現(xiàn)這一邏輯(例如叫JSBridge.layout.setHeader),下面代碼演示定制一個只有back鍵和標(biāo)題的導(dǎo)航組件:
// /h5/pages/index.js JSBridge.layout.setHeader({ background: { color: "#00FF00", opacity: 0.8 }, buttons: [ // 默認(rèn)只有back鍵,并且back鍵的默認(rèn)點擊處理函數(shù)就是back() { icon: "../images/back.png", width: 16, height: 16, onClick: function() { // todo... JSBridge.back(); } }, { text: "音樂首頁", color: "#00FF00", fontSize: 14, left: 10 } ] });
上面的接口,可以滿足絕大多數(shù)的需求,但是還有一些特殊的界面,通過H5代碼控制生成導(dǎo)航組件這種方式達(dá)不到需求:
如上圖所示,界面含有tab,且可以左右滑動切換,tab標(biāo)題的下劃線會跟著手勢左右滑動。大多見于app的首頁(mainActivity)或者分頻道首頁,這種界面一般采用定制webview的做法:定制的導(dǎo)航組件和內(nèi)容框架(為了支持左右滑動手勢),H5打開此類界面一般也是開特殊的API:
// /h5/pages/index.js // 開打音樂頻道下“我的音樂”tab JSBridge.view.openMusic({"tab": "personal"});
這種打開特殊的界面的API之所以特殊,是因為它內(nèi)部要么是純Native實現(xiàn),要么是和某個約定的html文件綁定,調(diào)用時打開指定的html。假設(shè)這個例子中,tab內(nèi)容是H5的,如果H5是SPA架構(gòu)的那么openMusic({"tab": "personal"})則對應(yīng)/music.html#personal這個url,反之多頁面的則可能對應(yīng)/mucic-personal.html。
至于一般的打開新界面,則有兩種可能:
app內(nèi)H5界面
指的是由app開發(fā)者開發(fā)的H5頁面,也即是app的功能界面,一般互相跳轉(zhuǎn)需要轉(zhuǎn)場動畫,打開方式是采用Native提供的接口打開,例如:
JSBridge.view.openUrl({ url: "/music-list.html", title: "音樂列表" });
再配合下面即將提到的離線訪問方式,基本可以做到模擬Native界面的效果。
第三方H5頁面
指的是app內(nèi)嵌的第三方頁面,一般由`a`標(biāo)簽直接打開,沒有轉(zhuǎn)場動畫,但是要求打開webview默認(rèn)的歷史列表,以免打開多個鏈接后點回退直接回到Native主界面。系統(tǒng)級UI組件采用Native
基于以下原因,一些通用的UI組件,如alert、toast等將采用Native來實現(xiàn):
H5本身有這些組件,但是通常比較簡陋,不能和APP UI風(fēng)格統(tǒng)一,需要再定制,比如alert組件背景增加遮罩層
H5來實現(xiàn)這些組件有時會存在坐標(biāo)、尺寸計算誤差,比如筆者之前遇到的是頁面load異常需要調(diào)用對話框組件提示,但是這時候頁面高度為0,所以會出現(xiàn)彈窗“消失”的現(xiàn)象
這些組件通常功能單一但是通用,適合做成公用組件整合到HybridApi里邊
下面代碼演示H5調(diào)用Native提供的UI組件:
JSBridge.ui.toast("Hello world!");默認(rèn)界面采用Native
由于H5是在H5容器里進(jìn)行加載和渲染,所以Native很容易對H5頁面的行為進(jìn)行監(jiān)控,包括進(jìn)度條、loading動畫、404監(jiān)控、5xx監(jiān)控、網(wǎng)絡(luò)診斷等,并且在H5加載異常時提供默認(rèn)界面供用戶操作,防止APP“假死”。
下面是微信的5xx界面示意:
設(shè)計H5容器Native除了負(fù)責(zé)部分界面開發(fā)和公共UI組件設(shè)計之外,作為H5的runtime,H5容器是hybrid架構(gòu)的核心部分,為了讓H5運(yùn)行更快速穩(wěn)定和健壯,還應(yīng)當(dāng)提供并但不局限于下面幾方面。
H5離線訪問之所以選擇hybrid方式來開發(fā),其中一個原因就是要解決webapp訪問慢的問題。即使我們的H5性能優(yōu)化做的再好服務(wù)器在牛逼,碰到蝸牛一樣的運(yùn)營商網(wǎng)絡(luò)你也沒轍,有時候還會碰到流氓運(yùn)營商再給webapp插點廣告。。。哎說多了都是淚。
離線訪問,顧名思義就是將H5預(yù)先放到用戶手機(jī),這樣訪問時就不會再走網(wǎng)絡(luò)從而做到看起來和Native APP一樣的快了。
但是離線機(jī)制絕不是把H5打包解壓到手機(jī)sd卡這么簡單粗暴,應(yīng)該解決以下幾個問題:
H5應(yīng)該有線上版本
作為訪問離線資源的降級方案,當(dāng)本地資源不存在的時候應(yīng)該走現(xiàn)網(wǎng)去拉取對應(yīng)資源,保證H5可用。另外就是,對于H5,我們不會把所有頁面都使用離線訪問,例如活動頁面,這類快速上線又快速下線的頁面,設(shè)計離線訪問方式開發(fā)周期比較高,也有可能是頁面完全是動態(tài)的,不同的用戶在不同的時間看到的頁面不一樣,沒法落地成靜態(tài)頁面,還有一類就是一些說明類的靜態(tài)頁面,更新頻率很小的,也沒必要做成離線占用手機(jī)存儲空間。
開發(fā)調(diào)試&抓包
我們知道,基于file協(xié)議開發(fā)是完全基于開發(fā)機(jī)的,代碼必須存放于物理機(jī)器,這意味著修改代碼需要push到sd卡再看效果,雖然可以通過假鏈接訪問開發(fā)機(jī)本地server發(fā)布時移除的方式,但是個人覺得還是太麻煩易出錯。
為了實現(xiàn)同一資源的線上和離線訪問,Native需要對H5的靜態(tài)資源請求進(jìn)行攔截判斷,將靜態(tài)資源“映射”到sd卡資源,即實現(xiàn)一個處理H5資源的本地路由,實現(xiàn)這一邏輯的模塊暫且稱之為Local Url Router,具體實現(xiàn)細(xì)節(jié)在文章后面。
H5離線動態(tài)更新機(jī)制將H5資源放置到本地離線訪問,最大的挑戰(zhàn)就是本地資源的動態(tài)更新如何設(shè)計,這部分可以說是最復(fù)雜的了,因為這同時涉及到H5、Native和服務(wù)器三方,覆蓋式離線更新示意圖如下:
解釋下上圖,開發(fā)階段H5代碼可以通過手機(jī)設(shè)置HTTP代理方式直接訪問開發(fā)機(jī)。完成開發(fā)之后,將H5代碼推送到管理平臺進(jìn)行構(gòu)建、打包,然后管理平臺再通過事先設(shè)計好的長連接通道將H5新版本信息推送給客戶端,客戶端收到更新指令后開始下載新包、對包進(jìn)行完整性校驗、merge回本地對應(yīng)的包,更新結(jié)束。
其中,管理平臺推送給客戶端的信息主要包括項目名(包名)、版本號、更新策略(增量or全量)、包CDN地址、MD5等。
通常來說,H5資源分為兩種,經(jīng)常更新的業(yè)務(wù)代碼和不經(jīng)常更新的框架、庫代碼和公用組件代碼,為了實現(xiàn)離線資源的共享,在H5打包時可以采用分包的策略,將公用部分多帶帶打包,在本地也是多帶帶存放,分包及合并示意圖:
Local Url Router離線資源更新的問題解決了,剩下的就是如何使用離線資源了。
上面已經(jīng)提到,對于H5的請求,線上和離線采用相同的url訪問,這就需要H5容器對H5的資源請求進(jìn)行攔截“映射”到本地,即Local Url Router。
Local Url Router主要負(fù)責(zé)H5靜態(tài)資源請求的分發(fā)(線上資源到sd卡資源的映射),但是不管是白名單還是過濾靜態(tài)文件類型,Native攔截規(guī)則和映射規(guī)則將變得比較復(fù)雜。這里,阿里去啊app的思路就比較贊,我們借鑒一下,將映射規(guī)則交給H5去生成:H5開發(fā)完成之后會掃描H5項目然后生成一份線上資源和離線資源路徑的映射表(souce-router.json),H5容器只需負(fù)責(zé)解析這個映射表即可。
H5資源包解壓之后在本地的目錄結(jié)構(gòu)類似:
$ cd h5 && tree . ├── js/ ├── css/ ├── img/ ├── pages │?? ├── index.html │?? └── list.html └── souce-router.json
souce-router.json的數(shù)據(jù)結(jié)構(gòu)類似:
{ "protocol": "http", "host": "o2o.xx.com", "localRoot": "[/storage/0/data/h5/o2o/]", "localFolder": "o2o.xx.com", "rules": { "/index.html": "pages/index.html", "/js/": "js/" } }
H5容器攔截到靜態(tài)資源請求時,如果本地有對應(yīng)的文件則直接讀取本地文件返回,否則發(fā)起HTTP請求獲取線上資源,如果設(shè)計完整一點還可以考慮同時開啟新線程去下載這個資源到本地,下次就走離線了。
下圖演示資源在app內(nèi)部的訪問流程圖:
其中proxy指的是開發(fā)時手機(jī)設(shè)置代理http代理到開發(fā)機(jī)。
數(shù)據(jù)通道上報
由于界面由H5和Native共同完成,界面上的用戶交互埋點數(shù)據(jù)最好由H5容器統(tǒng)一采集、上報,還有,由頁面跳轉(zhuǎn)產(chǎn)生的瀏覽軌跡(轉(zhuǎn)化漏斗),也由H5容器記錄和上報
ajax代理
因ajax受同源策略限制,可以在hybridApi層對ajax進(jìn)行統(tǒng)一封裝,同時兼容H5容器和瀏覽器runtime,采用更高效的通訊通道加速H5的數(shù)據(jù)傳輸
Native對H5的擴(kuò)展主要指擴(kuò)展H5的硬件接口調(diào)用能力,比如屏幕旋轉(zhuǎn)、攝像頭、麥克風(fēng)、位置服務(wù)等等,將Native的能力通過接口的形式提供給H5。
綜述最后來張圖總結(jié)下,hybrid客戶端整體架構(gòu)圖:
其中的Synchronize Service模塊表示和服務(wù)器的長連接通信模塊,用于接受服務(wù)器端各種推送,包括離線包等。Source Merge Service模塊表示對解壓后的H5資源進(jìn)行更新,包括增加文件、以舊換新以及刪除過期文件等。
可以看到,hybrid模式的app架構(gòu),最核心和最難的部分都是H5容器的設(shè)計。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/78409.html
閱讀 2860·2019-08-30 15:44
閱讀 1886·2019-08-29 13:59
閱讀 2845·2019-08-29 12:29
閱讀 1090·2019-08-26 13:57
閱讀 3201·2019-08-26 13:45
閱讀 3330·2019-08-26 10:28
閱讀 824·2019-08-26 10:18
閱讀 1694·2019-08-23 16:52