引言:在多元化的今天,一個熱門的移動app,或多或少都會有內在H5在其中。而對於一個有很多運營場景的app來說,這種情況更常見了。試想一下,如果在一個公司,存在很多native和H5同時需要開發的頁面,為了節省開發成本,此時如果只開發H5,就需要考慮native的體驗了,而這就是本文的目的,如何讓native端擁有像載入本地頁面一樣的速度去載入H5。
在app內載入H5速度慢一直是客戶端開發的痛點,拋開H5的體驗本身與native就有差距不說,如果載入速度還很慢,這將會對使用者體驗造成巨大影響。那麼像做到像native頁面一樣瞬間載入完H5,思路就會變得比較清晰了--提前在本地儲存遠端資源包。
方案選擇
從這個點出發,我們需要考慮,以怎樣的形式來提前拿到資源包(css,js,html,通用的圖片等),減少這些靜態資源的網路請求,增加載入速度。無非就是一下兩點:
1.將資源包在app打包階段直接植入
2.在執行時動態下載資源包
單純從業務層來說,如果你的業務夠簡單,其實第一種方式已經完全滿足,每次需要新增頁面就重新發版嘛,雖然顯得有點愚笨,但是還是能滿足的。
但是從長遠的角度來說,我們要做到儘可能的動態化,動態化是客戶端的熱點,我們要做到儘量不依賴於版本更新來實現動態化。對於iOS來說,更新機制本身就非常緩慢,要通過app store的稽核有時候還需要靠人品,更何況使用者也不一定買賬,他們不一定會更新我們的app。在這樣的情況下,第二種方案就會顯得更加友好。
設計載入流程
那麼,該怎麼設計一套完整的解決方案來滿足執行時動態下載資源包呢。
抽出細節,大體上可以歸結為下圖所示的結構圖:
我來解釋下這個圖,我是建立在客戶端已經實現socket層協議,所以能與server保持長連線以至於server能主動push資料的情況,實現這種協議蠻複雜的。實際上如果沒有這個協議,那就需要client找時機主動去server請求(app啟動時請求一次?或者是每隔一段時間請求一次,取決於你),本文以後者為例。
下面我來演示下一個完整的下載新資源包的過程:
1.運營小妹覺得某節日要到了,需要釋出一個新的頁面,然後在運營後臺生成資源包,運營後臺會自動更新config,其中包括資源包的version,是否強制關閉載入本地資源包(降級策略,防止這個元件本身有BUG),還有一些hotpatch指令碼。並且將資源包根據裡面的內容部署到remote database。
2.在合適的時機,client發起http請求向server查詢是否有新版本的資源包,並帶上本地的config。
3.server根據config裡的選項,比對從client拿到的config,發現客戶端是舊版本的config,OK,則下發新的config給client,並且傳送從database裡拿到的資源包(為了加快速度,可以部署到CDN)。
4.client拿到最新的資源包後,在本地進行解密,解壓等操作,並對映成對應ULR相對於本地的local file url。比如:www.baidu.com這個網址下的靜態資原始檔在本地的的file://dsalkfjsld…
至此,已經完成資源包的下載。
攔截並載入本地資源包
那麼有了資源包後,怎麼能讓app像native頁面的速度去載入H5呢。
其實原理就是對H5請求進行攔截,如果本地已經有對應的靜態資原始檔,則直接載入,這樣就能達到“秒開”webview的效果。
對於iOS而言,這就需要用到NSURLProtocol這個神器了。接下來,分析下它到底是什麼東西,我們怎麼利用它達到上述效果。
NSURLProtocol能夠讓你去重新定義蘋果的URL載入系統(URL Loading System)的行為,URL Loading System裡有許多類用於處理URL請求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等。當URL Loading System使用NSURLRequest去獲取資源的時候,它會建立一個NSURLProtocol子類的例項,你不應該直接例項化一個NSURLProtocol,NSURLProtocol看起來像是一個協議,但其實這是一個類,而且必須使用該類的子類,並且需要被註冊。
--從網上拷貝的
換句話說,NSURLProtocol能攔截所有當前app下的網路請求,並且能自定義地進行處理。
廢物不多說,上程式碼:
這裡只介紹與我們需求相關的NSURLProtocol方法。
搞了這麼多,其實最核心的就是前四個方法:
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
這個方法的作用是判斷當前protocol是否要對這個request進行處理(所有的網路請求都會走到這裡,所以我們只需要對我們產生的request進行處理即可)。
+ (NSURLRequest )canonicalRequestForRequest:(NSURLRequest )request
這個方法其實很強大,它可以對request進行預處理,比如對header加一些東西什麼的,我們這裡沒什麼要改的,所以直接返回request就好了。
- (void)startLoading
重點是這個方法,我們這裡需要做一件事,就是自己拼裝httpResponse,並且返回給url load system,然後到了webview那一層,會收到response,對於webview而言,載入本地和走網路拿到的response是完全一樣的。所以上述程式碼展示瞭如何拼裝一個httpResponse,當組裝完成後,需要呼叫self.client將資料傳出去。
何為self.client,這個東西其實就是protocol與url load system互動的一個物件,系統提供給我們的,這樣理解就夠了。
需要注意的是,細心的讀者會看到else裡會有一段程式碼:
[NSURLProtocol setProperty:@YES forKey:WDHybridResourceProtocolHandledKey inRequest:newRequest];
這個是幹什麼用的?else的作用是當本地不存在這個檔案時,則主動重新發請求,此時又會呼叫canInitWithRequest,如果不設定flag,則會無限遞迴了。所以你懂得。
當然,重新發請求自然要實現NSURLConnectionDelegate。
總結
至此,如何快速載入H5已經全部介紹完畢。
附上前後載入速度對比:
好的,下次再見!
歡迎大家關注微博@kuailejim,我會經常在上面分享一些大家感興趣的東西。