先上圖一張:
前言
作為一種混合開發的模式,Hybrid APP底層依賴于Native提供的容器(UIWebview),上層使用Html&Css&JS做業務開發,底層透明化、上層多多樣化,這種場景非常有利于前端介入,H5的低成本、高效率、跨平臺等特性非常適合業務快速迭代。
在設計Hybrid之初,首先是要考慮本地化包的版本管理,如下圖所示,正常APP啟動會如下邏輯:
1.網頁資源提前加載到本地
在App啟動時會請求一個配置文件,包含版本號和包地址,舉例我們的文件如下:
{"errorMsg": "", "code": 0, "data": [{"zipDownloadUrl": "https://192.168.10.10/cdn/updatepkg/matrix_v2.2.606.zip", "version": "2.2.606", "packageName": "dist"}]}
這個配置文件每次啟動會去下載,并比對version跟本地的version是否一致,不一致則下載,一致則略過。
部分對用戶體驗要求較高的頁面使用原生開發
我和我的團隊在對用戶體驗要求較高的頁面(比如首頁),我們最好還是使用原生開發。這也是Hybrid的真正意義所在吧。但隨之也會產生一個新的問題:原生頁面和H5的跳轉實現。
1.http和https的處理
由于現在對網絡安全的重視(當然蘋果也由于要求),越來越多的App加入了https的支持。有一種非常普遍的問題需要我們注意:跨域。舉個例子,我們的UIWebView加載的頁面是https://www.baidu.com,但baidu.com這個頁面中的某段js代碼會執行頁面跳轉,而跳轉的協議是hybrid,也就是諸如hybrid://AViewController類似的URL地址。App意識到這可能是一段不安全的代碼,因此不給于執行,之前就是遇到了這個問題導致我們App不能響應任何【H5->原生】跳轉。那如何“魚和熊掌兼得”呢,解決方案有兩種,各有利弊:
在調用UIWebView的loadRequest方法時,先去判斷本地有沒有資源,有的話加載本地資源,這樣其實就變相的走了http協議或者file協議,就不存在https跨域的問題。但是對于UIWebView的js請求我們還是要進行攔截,走本地的https請求即可。這個方案的優點在于,由于html文件資源在本地所以加載資源速度比較快,用戶體驗不錯;缺點就是我們必須通過URLProtocol重新定義所有的請求,這就需要我們在Request請求的時候避免在httpbody或者httpbodystream中設置參數,轉而在header中設置。另外一種解決方案是,請求的時候將URL地址直接從https替換成http,這樣的好處就是不需要定義所有的網絡請求,但由于html文件資源在遠端所以加載資源速度比較慢。
2.參數的傳遞
在原生和H5之間跳轉的時候,參數傳遞都作為URL地址的一部分,例如AViewController中有屬性username和tel,那我們在跳轉到AViewController是路由的寫法應該是類似:
hybrid://AViewController?username=123&tel=1526181629x
這個很好理解,但有種情況,如果接受的是個url,并且url中也有參數怎么辦?舉例
hybrid://AViewController?url=http://www.baidu.com?username=123&tel=1526181629x
那請問,tel是AViewController的參數還是url自帶的參數?這是個問題。解決方案其實也很簡單:一層層encode,比如有一個url那就encode一次,如果url中還帶url那再encode一次。decode的時候也是一樣,一層一層decode。
使用原生SDK編寫一套可供js調用的API
路由除了可以用于原生頁面和H5頁面跳轉,還可以用于調用本地的一些功能,下面是我們App預設的一些服務:
// 打開新頁面
#define SERVICE_OPENNEWPAGE @"hybrid://openNewPage?param="
// 更新導航欄
#define SERVICE_UPDATEUIHEADER @"hybrid://updateNavigationBar?param="
// 回退
#define SERVICE_PAGEBACK @"hybrid://back?param="
// 獲取定位
#define SERVICE_GETLOCATION @"hybrid://getLocation?param="
// 獲取網絡類型,狀態
#define SERVICE_NETWORKTYPE @"hybrid://getNetWorkType?param="
因此只要在html中指明如上的鏈接即可。有時我們需要在調用服務成功后給個反饋,那就需要在native調H5的方法,幸好JSContext幫我們實現了這個想法。
如圖,當一個Request 發起后(這個Request 可以是UIWebView中的某個鏈接點擊產生,也可以UIWebView的Ajax請求),通過Watermelon 進行攔截,如果是正常的HTTP請求則放過,如果是我們定義的scheme則單獨做處理。OHHttpStub是基于NSURLProtocol實現的URL Request 攔截系統。方法
+(id)stubRequestsPassingTest:(OHHTTPStubsTestBlock)testBlock withStubResponse:(OHHTTPStubsResponseBlock)responseBlock;
而參數testBlock就是需要攔截的Request請求,攔截后我們可以自己實現自己的Request請求,并返回數據給responseBlock。至于這里為什么使用OHHttpStub不使用原生的NSURLProtocol是為了避免與其他的使用NSURLProtocol 的三方庫(比如Bulgly)沖突。當攔截成功后,我們再把結果分發給WebView。
總結:事實上,Hybrid的核心有3個
1.離線資源包的管理。
2.webview對所有請求的攔截(Native核心功能)
3.交互協議的制定
一些問題遇到的問題:
事實上,在iOS開發當中,你會發現如果你采用了WKwebview會后會出現各種各樣的問題,比如說跨域的問題(可以多了解http協議本身,這個問題已經解決),比如說某些彈框失敗的問題包括cookie的問題,當然這都是wk范疇的問題,有些在我后來的實踐當中已經解決了,有些還沒有,我后面有時間的話會把這些解決方案逐一分享給大家。當然目前針對上述問題我發現包括當前開源的無論是豆瓣還是騰訊團隊的iOS版Hybrid框架都沒有解決這個問題,統一采用的UIwebview控件,我個人以為使用WKwebview一定是大勢所趨,所有問題一定會要解決畢竟技術是需要一步步來推動的。最后來讓大家看一下我們之前團隊做的這個開源的Hybrid框架源碼,代碼寫的很一般,希望大家不要噴,它只是給了一個設計思路以供大家參考而已: