一個Hybrid框架的搭建思路

本系列博客是本人的開發筆記。爲了方便討論,本人新建了一個微信羣(iOS技術討論羣),想要加入的,請添加本人微信:zhujinhui207407,【加我前請備註:iOS 】,本人博客http://www.kyson.cn 也在不停的更新中,歡迎一起討論

不論iOS還是Android,都有瀏覽器,對應到開發中有個控件叫做UIWebView,在App開發中摒棄原生頁面編寫,而在App中嵌入UIWebView,使用開發網頁的方式開發App,這就是所謂的Hybrid開發吧。
這種開發模式最大的好處是資源在服務端,迭代速度快,不需要頻繁提交審覈。缺點是:
1.由於HTML等網頁資源在服務端,所以加載速度有點慢,用戶體驗欠佳
2.HTML的能力有限,不能或者有限的能調用諸如定位,藍牙,支付等功能
3.UIWebView在各個系統版本上的兼容性問題
圍繞上面的這幾個問題,我們做了如下事情

網頁資源提前加載到本地

在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的跳轉實現。
原生->H5,只需要進入一個帶UIWebView的原生頁面即可
H5->原生,這個有點複雜,需要定義一套協議用於頁面跳轉。
網上的參考文檔不少,比較有名的是葉小釵的博客:http://www.cnblogs.com/yexiaochai/p/4921635.html
這裏我在添加一些我們團隊總結的吧
<font color="red">1.http和https的處理</font>
由於現在對網絡安全的重視(當然蘋果也由於要求),越來越多的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文件資源在遠端所以加載資源速度比較慢。
<font color="red">2.參數的傳遞</font>
在原生和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頁面跳轉,還可以用於調用本地的一些功能(或者稱“API”或者 “服務”,是的,我習慣稱“服務”這種說法),下面是我們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幫我們實現了這個想法。

應對UIWebView在各個系統版本上的兼容性問題

這個問題基本是無解的:在iOS9.1中,如果調用UIWebView的goback方法是不起作用的,是的,很遺憾這是UIWebView的一個Bug,因此儘量少的在一個頁面中進行html的跳轉是唯一的解決方案。如果你夠棒,可以將一些開源的瀏覽器內核寫進你的App,除此之外你毫無辦法。

好了,基本的理論知識已經完了,框架的代碼可以在這裏獲取:
https://github.com/zjh171/Watermelon
下面我們來看下我們的這個西瓜框架。

如圖,當一個Request 發起後(這個Request 可以是UIWebView中的某個鏈接點擊產生,也可以UIWebView的Ajax請求),通過Watermelon 進行攔截,如果是正常的HTTP請求則放過,如果是我們定義的scheme則單獨做處理。OHHttpStub是基於NSURLProtocol實現的URL Request 攔截系統。方法

+(id<OHHTTPStubsDescriptor>)stubRequestsPassingTest:(OHHTTPStubsTestBlock)testBlock
                                   withStubResponse:(OHHTTPStubsResponseBlock)responseBlock;

而參數testBlock就是需要攔截的Request請求,攔截後我們可以自己實現自己的Request請求,並返回數據給responseBlock。至於這裏爲什麼使用OHHttpStub不使用原生的NSURLProtocol是爲了避免與其他的使用NSURLProtocol 的三方庫(比如Bulgly)衝突。當攔截成功後,我們再把結果分發給WebView。

WebView接受到數據就解析Request請求中的數據,並做相應處理。大概的流程就是這樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章