本文是公衆號文章直接貼過來的,發現圖片都沒自動上傳,文章最後有個人公衆號二維碼(或者搜索關注:sanshuiqing123),關注個人公衆號,查看歷史文章。前端
上次文章介紹了Hybrid開發中經常使用到的Web和NA的通訊方案:JSBridge,經過比較以後,最終推薦安卓和iOS通用的scheme協議,能夠保證APP內和外均可以使用,今天開始正式介紹整個Hybrid架構內容。就像本系列「開篇語」提到的,這裏的Hybrid是「狹義的Hybrid」,而不是全部的NA套個webapp就是Hybrid。git
Hybrid技術體系是一套不少技術組成的完整知識架構。拿手百Hybrid方案,包括規範/約定、開發、聯調、服務支撐等整個開發流程,而隨着PWA之類「新」技術的產生又在考研技術架構面向將來的設計能力,因此手百Hybrid還在不斷完善,注意是不斷完善,不是推到重來!手百Hybrid技術總體架構以下:github
接下來系列會圍繞本架構圖中重要的部分依次展開,全面的介紹Hybrid開發知識,本文介紹模板本地化開發方案。web
所謂「模板本地化」,就是將Web頁面內置到APP內,隨版發佈上線,而後經過雲端接口實現更新,再直白一點:將H5網站的頁面預先放到客戶端發版,而後雲端更新新版本。ajax
這個方案好處在於:redis
本地化模板提升頁面打開速度,減小用戶等待時間算法
模板可更新,版本控制更方即可控,收斂快chrome
Web頁面和NA內置代碼實現一套代碼,減小開發成本數據庫
上手成本小,開發就是實際開發Web(H5)頁面,經過構建工具,生成Web頁面和NA模板包不一樣的代碼json
標準H5代碼,遷移成本小,經過Node和構建工具,能夠作到H5版本先後同構,未來還能夠不改代碼的前提下適配PWA
本文提到的JSBridge調起協議是統一使用 hybrid://
開頭,只是提供思路和介紹總體Hybrid模板包架構和用到的技術,不涉及到具體代碼,可是文章保證乾貨和誠意都滿滿。
本文會介紹兩種模板本地化方案,分別是:依賴客戶端攔截器(proxy)的方案一;徹底無域名限制的方案二,兩個方案的實現流程以下:

經過流程圖來看,方案一比方案二多了攔截器。
方案一是「可感知」的,根據本地緩存的模板包域名(可下發、可徹底根據緩存的模板域名)進行過濾,若是本地有則讀取本地緩存,若是本地沒有則仍是訪問線上(無損)。
方案二 是「預置型」的方案,只有發版時候規定的某些頁面或者頻道(插件)開通了Hybrid功能,才能使用Hybrid模板包緩存。
根據上面提到的兩種方案,對應的模板包打包內容也不同。具體包內容目錄結構以下所示:
方案一:攔截器 | 方案二:無攔截器 | |
---|---|---|
cookie | 帶域名符合http規範 | 須要native支持 |
入口開放 | 方便,無需發版 | 須要發版 |
NA開發成本 | 攔截器開發 | 瀏覽器能力對齊 |
開發聯調成本 | 較低 | 較高 |
webapp改形成本 | 方便 | 不方便 |
安全性 | 域名限制 | 須要獨立控制 |
打包方案 | 按照網站目錄打包 | 按照插件/頻道打包 |
擴展性 | 依照PWA模式擴展能力 | - |
方案一相對來講客戶端開發量較大,並且攔截域名太多會有性能問題,可是優勢也相對較多一些,具體須要本身權衡,再次強調,本系列本意是掃盲和普及Hybrid開發知識,儘可能全面的介紹Hybrid技術,不針對某種方案深刻展開,後續討論內容也是如此。
模板本地化以後,客戶端實際是經過 file://
協議訪問本地模板,這樣會致使跟域名相關的操做和方法須要客戶端提供端能力支持,目前最重要的是一下幾個:
http請求:若是在file協議下直接請求http[s]會引發跨域(Tips:跨域的幾種狀況有哪些?
cookie操做:域名沒了,cookie怎麼辦?session也是cookie的實現
localstorage/sessionstorage:這倆是根據域名劃分存儲空間和讀寫的
這些瀏覽器的基本能力須要依靠客戶端爲頁面提供端能力來補齊。瀏覽器根據域名作了限制的端能力,是遵照「同源策略」,因此在補齊端能力的時候,應該考慮進去「同源策略」,不能無節制的開通端能力
考慮到同源策略,每一個模板包會對應一個域名限制(系列文章中上一篇提到過JSBridge的安全鑑權問題),兩種方案分別對應的同源策略解決方案是:
方案一攔截器:代理的域名就是要限制的域名範圍
方案二無攔截器:在package.json/manifest.json中添加域名權限申請,好比 domain:xxx.baidu.com
根據本系列上篇JSBridge的最佳實踐結合經常使用的請求方式,設計http請求的接口以下:
GET:hybrid://http/get?url=http://baidu.com&query={key1:value1,key2:value2}
POST:hybrid://http/post?url=http://baidu.com&data={key1:value1,key2:value2}
從協議上來看,post和get並無差別,可是hybrid的JSBridge協議設計是基於URL schema協議的,因此會有長度限制,對於簡單的post操做使用這個協議就能夠,可是對於post的data太大的狀況下(不考慮上傳文件等多媒體類POST操做,這方面能夠經過後面文章提到的device API加強解決),須要提供
爲了防止網站cookie被惡意修改,不少網站已經使用httponly的方式來操做cookie,這種狀況下cookie實際上是沒有必要經過前端使用js操做的,並且在 file://
協議下發送 http[s]://
API請求,http協議也會帶上域名和路徑下的cookie,因此cookie的操做我是不建議開端能力的。可是凡事都有可是。。。cookie操做仍是設計上吧:
hybrid://cookie/[get/set/delete]?name=value,expire=xxx;name2=value2,path=/
複製代碼
瀏覽器緩存的設計參考web storage的key-value存儲設計,而後增長過時時間,相似memcached這類內存緩存,由於value是string類型,因此不像redis那樣支持多種存儲類型格式。
hybrid://cache/save?key=xxx&value=xxx&callback=xxx[&expire=xxx]
複製代碼hybrid://cache/get?key=xxx&callback=xxx
複製代碼hybrid://cache/delete?key=xxx&callback=xxx複製代碼
爲了保證後續一套代碼,在不一樣的構建流程打出不同的模板包(好比H5版本和hybrid版本),構建過程再也不是簡單的壓縮資源,而是根據打包類型對組件進行有選擇性的pick,這裏咱們使用 FIS3
,根據不一樣的 media
打出不同的包。例如:
fis.media('hybrid').match(…)複製代碼fis.media('webapp').match(…)複製代碼
詳細用法請查看fis.baidu.com ,這裏再也不展開。
咱們的H5代碼和Hybrid的代碼都在一塊兒,徹底組件化以後,除了Hybrid的差別化的功能性組件,其餘組件都是能夠通用的。這些差別化的組件包括:
上一章節提到的Http、cache和cookie這類跟域名相關的代碼,咱們分別封裝了Hybrid內和H5版本,好比Http都叫Fetch,可是內部實現:hybrid是調用NA接口,H5是Fetch+ajax
瀏覽和導航類:好比跳轉/打開新頁面,NA內是打開一個新的webview,H5是 location
跳轉或直接A標籤
端能力和加強類組件:一樣是大圖瀏覽器,爲了更好的交互,NA上作了加強,H5是純web實現
這些差別性的代碼都封裝在不一樣的組件內,經過設計模式中的「接口模式」對外暴漏同樣的接口和使用方法。
這種設計,能夠更好的提升業務同事學習和編寫程序的成本,同時統一的組件化管理方便單元測試和聯調,(劃重點:等本系列聯調部分會再重提這種設計的好處,那時候會發現這設計太讚了!)
這裏安利集鵠大叔的jdists(有對應的fis插件):https://github.com/zswang/jdists
經過jdists能夠經過註釋的方式對代碼進行區域化裁剪和聯調功能,好比下面代碼能夠經過fis3的media來觸發trigger,從而在不一樣的條件下暴漏不一樣的代碼區域:
/*<debug>*/複製代碼console.log(debug);複製代碼/*</debug>*/複製代碼/*<jdists trigger="hybrid">複製代碼var fetch = require('na/fetch')複製代碼</jdists>*/複製代碼/*<jdists trigger="webapp">複製代碼var fetch = require('web/fetch')複製代碼</jdists>*/複製代碼
除了代碼pick以外,構建工具還要和模板包管理平臺配合,增長額外的文件生成(好比生成版本號、簽名、diff包)作到徹底自動化,而後包管理平臺去拉取構建工具生成的zip包,上傳到CDN平臺,在數據庫建立一條記錄,關於模板包管理平臺的內容,在下一篇文章詳細介紹。
模板包設計好了以後,就須要考慮下發和更新的流程。除了考慮模板的更新,還須要考慮到容錯機制,保證模板包升級不斷出現問題或者某些極端狀況下,服務可用。
模板更新時機是很講究的,不一樣的APP產品能夠利用的時機可能不同,目前針對手百產品合適的時機有:
APP冷熱啓動
接口檢測
頻道異步檢查,當打開某個Hybrid頻道後臺開始更新
頁面主動檢測
推送
綜合來講,無論怎樣的更新時機,都逃不出兩種更新的途徑:
經過專門的NA更新接口上傳本地版本號,server下發最新版本信息,徹底有NA完成模板包更新
經過頁面JS接口主動檢測進行更新
第一種途徑,好比冷熱啓動、接口和頻道異步都是經過上行請求將APP內單個或者多個頻道的當前模板版本號上傳給server,server根據模板維護cms推送過來的最新版本挨個頻道依次比較版本號,若是版本號有更新,就下發對應的最新流量包地址和校驗信息。
而頁面主動檢測包括兩種:
在頁面發起請求的API接口中帶有當前的版本號,若是接口判斷有更新,則下發對應的更新包信息(下載地址和校驗信息),而後頁面js經過NA接口通知客戶端更新
頁面js直接調用NA接口強制進行一次單獨的檢測,這樣客戶端會帶着版本號走專門的接口只進行模板版本的更新檢測,而後走NA的更新流程
當得知某個版本號存在不可修復的bug,須要緊急容錯,能夠經過server下發對應的command(app調起協議,也是一種schema)來指定在某個版本(模板版本、客戶端版本)或者版本號範圍內不調用Hybrid,而直接訪問H5頁面,達到快速止損。
舉例說明:新聞列表是NA實現的,新聞的詳情頁是Hybrid的作的,因此Hybrid的上游是NA作的list,Hybrid詳情頁是經過NA的列表調起展示的。NA的list數據和調起協議(command)是由server下發的,若是server下發的是調起Hybrid頁面,那麼用戶點擊list打開Hybrid詳情頁;當Hybrid版本在某些狀況下有問題(好比在某些客戶端版本上有bug或者直接Hybrid某版本包就有bug),那麼Server下發給NA調起詳情頁的Command就變成直接打開H5 webapp,而不是打開Hybrid,從而將有問題的Hybrid切換到線上H5,達到容錯止損。
除了server端止損以外,咱們還設計了回滾機制,在包管理平臺,能夠將任意一個已經上線的版本包做爲回滾包,而後生成新的版本號進行下發回滾。
客戶端在安裝更新包以前,先將老的版本包進行備份,而後開始安裝,若是安裝過程碰見問題,好比覆蓋失敗,或者空間不夠等各類問題,就須要刪除以前的安裝操做,將備份包從新解壓,保證整個安裝過程是完整的,而不是安裝一半。
針對模板包的收斂率率,除了更新時機以外,還應該從鏈路優化和包體積方面進行優化。
具體策略以下所列:
針對模板包作專門的通訊線路和協議優化
使用CDN就近存儲
斷點續傳
分塊下載
重試策略
減少包體積
增量更新包設計
增量包的設計通常有兩種方式,能夠根據不一樣的場景和開發週期進行選擇:
按文件diff進行增量下發
按二進制包進行增量下發
這種方式對於打包工具來講很簡單,並且客戶端沒有開發工做量。
客戶端拿到模板更新包是總體覆蓋安裝的,覆蓋安裝的意思是:碰見新的文件就直接新增,碰見重名的文件就直接覆蓋安裝。這就有點像前端靜態資源管理,能夠打md5進行增量,能夠同名覆蓋。
咱們作法是當發版的時候,編譯工具打出一個全量包,而後從版本庫中找到最近的3次版本包,對文件進行遍歷,找出diff,而後造成 Vn-Vn+1
的diff包,而這一切都是自動維護的。
我什麼是最近3個版本包作diff,而不是所有?
由於若是所有隨着發版愈來愈多,diff包會呈現指數增加,增長維護成本;並且模板收斂好了,兩個以前老版本的量就很小了,沒有必要爲這些版本作增量包;再說全量包也不會大到哪裏去嘛~
這種方式是將模板zip包,根據bsdiff差分算法打出不一樣的patch,而後將下發給客戶端,客戶端再加上上一個版本的包生成新的全量包。
兩種方式各有利弊,實際應用要權衡成本和收益。
模板包在下發的過程當中,會碰見被篡改的問題,並且包若是不完整,也會對Hybrid模板包的完整性和功能構成威脅。
首先整個模板包的更新接口和模板包zip的CDN都是使用HTTPS。另外在包安全性方面咱們分別作了兩個校驗:
模板完整性檢測
模板合法性校驗
結合啓動更新流程(客戶端和server交互部分)來說下:

在server升級接口會返回兩個最重要的字段: md5
和 signature
。
md5用於校驗zip的完整性,保證zip是完整的,可解壓的;
signature是簽名,簽名算法是結合私鑰、請求參數、版本號、客戶端特徵值和模板包的md5等組合計算的,簽名算法是保證了包的安全性;這樣即便包被劫持替換,不知道簽名算法也不會經過校驗。

知識是相通的,要學會觸類旁通,文章爲了通俗易懂,不少技術介紹根據前端常見的場景作了類比。只要有心,涉獵範圍夠廣,你會發現:
寫過chrome擴展的對於模板本地化方案是否是似曾相識?
若是以爲和開發chrome擴展相似,能不能利用chrome的調試功能進行調試?
而模板本地化方案又跟PWA是否是能夠通用?
PWA解決的痛點是什麼?和本文的模板本地化方案怎麼快速切換?
這些問題都值得深刻思考,且聽後續文章慢慢道來~