1. 程式人生 > 其它 >WKWebView 載入生命週期與代理方法剖析

WKWebView 載入生命週期與代理方法剖析

1. 前言

從WebView開始載入一條請求,到頁面完整呈現這一過程發生了什麼?無論是做WebView效能優化還是異常問題監控與排查,我們都離不開對這一問題的思考與探索。

在本篇文章中,我們將在上一篇深入理解 WKWebView(入門篇)—— WebKit 原始碼除錯與分析》的基礎上,結合 iOS 端 WKWebView 的 WKNavigationDelegate 代理方法,站在移動端的視角深入分析WKWebView 網路請求載入的生命週期流程,給大家提供更多業務上的思路。

2. iOS端WebKit載入框架

深入理解 WKWebView(入門篇)—— WebKit 原始碼除錯與分析

》中我們通過 Demo 驗證了WebKit三大程序工作模型,並簡述了三大程序的主要職責。接下來我們將展開描述下其中的一些細節。

三大程序間通訊關係圖

如上圖所示,說明了UIProcess、WebContent、NetworkProcess三大程序間的通訊關係,並列舉了他們的主要職責。本系列的原始碼剖析工作始終圍繞三大程序,對其作進一步說明:

  • NetworkProcess程序:主要負責網路請求載入,所有的網頁共享這一程序。與原生網路請求開發一致,NetworkProcess也是通過封裝的NSURLSession發起並管理網路請求的。但不同的是,這一過程中有較多的網路進度的回撥工作以及各類網路協議管理,比如資源快取協議、HSTS 協議、cookie 管理協議等。

  • WebContent程序:主要負責頁面資源的管理,包含前進後退歷史,pageCache,頁面資源的解析、渲染。並把該程序中的各類事件通過代理方式通知給UIProcess。

  • UIProcess程序:主要負責WebContent 進行互動,與 APP 在同一程序中,可以進行WebView的功能配置,並接收來自WebContent程序的各類訊息,配合業務程式碼執行任務的決策,例如是否發起請求,是否接受響應等。

理解了三大程序的主要工作職責後,接下來,我們首先結合三大程序描述WebKit從網路載入到渲染的全流程,讓讀者對網頁載入有一個巨集觀上的理解。

3. iOS端WebKit載入流程

我們使用如下方法,從UIProcess層通過loadReqeust方法發起頁面載入請求(此處 request 只能是 get 請求,如果配置為 post 請求,WebKit 核心基於效能考慮,在跨程序傳輸時,會將 body 資料丟棄,導致異常)。

[self.webView loadRequest:request];

通過跟蹤WebKit原始碼,我們提取核心步驟如下:

  1. UIProcess中的loadRequest首先會觸發NetworkProcess程序建立,然後通過程序間通訊的方式將request傳送給NetworkProcess程序進行preconnect預連結操作,通過網路三次握手建立TCP連結,以便加快後續網路資源請求速度。

  2. UIProcess通過程序間通訊的方式將request傳送給WebContent程序,WebContent程序建立DocumentLoader載入器載入網路請求,並取消上個頁面的所有還在載入的請求,然後通過字典綁定當前頁面ID與建立好的NetworkProcss程序(便於服務端資料返回時,查詢資料回填所對應的頁面),最終將請求交付給NetworkProcess中的NSURLSession進行處理。

  3. NetworkProcess通過NSURLSession複用之前preconnect預連結,繼續進行網路載入,此時等待網路請求返回,網路層會繼續將資料通過程序間通訊方式傳輸給WebContent程序進行處理,開始流式進行資料解析,一邊接收一邊處理,進行詞法分析、語法分析,並在這一過程中載入解析出來的 js、css、圖片、字型等子資源,最終動態的生成(DOM 樹與 CSSOM 樹合成)渲染樹,在這一過程中,每次接受到新資料導致渲染樹有變更後,就會觸發一次checkAndDispatchDidReachVisuallyNonEmptyState方法,檢查當前頁面是否達到上屏狀態,若達到上屏狀態就進行上屏渲染。

達到上屏狀態的條件如下:

  1. 如果返回的 data 是普通文字文字,或返回的資料中包含普通文字文字,那隻需要達到非空200位元組即可以觸發上屏渲染;

  2. 如果返回的 data 是圖片資源類,則判斷畫素大小 > 32*32,即可觸發上屏渲染;

  3. 如果不滿足以上條件,對於主文件,判斷後面是否繼續接收資料,如果不繼續,則觸發上屏渲染;如後續還有資料,則迴圈上述流程直至觸發上屏。渲染完成,整個載入過程結束。

WebKit載入流程

對首屏渲染感興趣的同學可以嘗試配合服務端來針對部分場景(例如文字、圖片)做一些資料分包優化,或許會有一些不錯的收穫。

在描述完網頁核心載入過程後,為了更貼近我們日常的開發工作,接下來我們將重點描述以上工作流程如何與UIProcess程序(APP程序)關聯起來。

4. 載入生命週期代理方法

4.1 WKNavigationDelegate 方法簡要介紹

@protocolWKNavigationDelegate<NSObject>
@optional// 請求之前,決定是否要跳轉:使用者點選網頁上的連結,需要開啟新頁面時,將先呼叫這個方法。- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
// 頁面開始載入時呼叫- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 接收到響應資料後,決定是否跳轉- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
//主機地址被重定向時呼叫- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
//當開始載入主文件資料失敗時呼叫- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
//當內容開始返回時呼叫- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
//頁面載入完畢時呼叫- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
// 當主文件已committed時,如果發生錯誤將進行呼叫- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
// 如果需要證書驗證,進行驗證,一般使用預設證書策略即可 - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler;
// 9.0才能使用,web內容處理中斷時會觸發,可針對該情況進行reload操作,可解決部分白屏問題 - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);
@end

以上是WKNavigationDelegate代理方法及蘋果官方介面描述,足夠應付日常的開發工作了,但細節描述上有些粗糙,不能讓我們寫出踏實放心的程式碼,因此我們需要徹底理解這些方法背後的執行邏輯。接下來,我們將結合實踐與原始碼除錯對重點方法進行剖析。

4.2 深入理解WKNavigationDelegate 方法

WKNavigationDelegate代理方法呼叫流程

如上圖所示,描述了WKNavigationDelegate代理方法的呼叫流程,我們將在該圖的基礎上,重點描述帶顏色標註的代理方法,如下:

1)decidePolicyForNavigationAction 剖析

如第3節講述的網頁載入流程,當WebContent即將建立DocumentLoader載入器時,會首先觸發decidePolicyForNavigationAction代理方法。如果我們選擇cancel,那麼瀏覽核心會完全忽略這一操作,後續也不再繼續執行其他操作,我們可以放心的使用cancel取消掉我們不想載入的主文件請求,而無需擔憂任何異常。但當我們選擇alllow後,我們會進入一個稍微複雜的邏輯判斷,核心程式碼首先判斷該該連結是否是universalLink型別的連結,如果判斷是universalLink型別的連結,會嘗試去調起三方 app,如果能調起,則會cancel當前請求,否則才會走到正常的網路載入邏輯(如果需要統計 universalLink 調起情況與或建設遮蔽能力,可以再仔細閱讀該處原始碼)。

2)didStartProvisionalNavigation 理解

decidePolicyForNavigationAction方法中選擇allow並且判斷為非universalLink連結後,會立即觸發didStartProvisionalNavigation方法,表示即將開始載入主文件。這個方法看似只是對decidePolicyForNavigationAction方法的確認,但是值得思考的問題是方法名中的Provisional究竟是什麼意思。其實,頁面開始頁面載入後為了更好的區分載入的各階段,會將網路載入的初始階段命名為臨時狀態,此時的頁面是不會記入歷史的,直到接收到首個數據包,才會對當前頁面進行committed提交,並觸發didCommitNavigation方法通知UIProcess程序該事件,同時將網路 data 提交給WebContent進行渲染樹生成。我們可由此引申出下一個問題,即didFailProvisionalNavigation與didFailNavigation的關係。

3)didFailProvisionalNavigation 與 didFailNavigation 的分別在什麼時候執行?他們之間有什麼關係?

當NetworkProcess程序發生網路錯誤時,錯誤首先由NSURLSession回撥到WebContent層。WebContent會判斷當前主文件載入狀態,如果處於臨時態,則錯誤會回撥給didFailProvisionalNavigation方法;如果處於提交態,則錯誤會回撥給didFailNavigation方法。

主文件載入狀態圖

4)didFinishNavigation 究竟什麼時候執行?與頁面上屏是否有關?

在上面的描述中,我們已經理解了NetworkProcess層也是使用NSURLSession載入主文件的。當NSURLSession接收到finish事件時,會將該訊息通過程序通訊方式傳遞給WebContent程序,WebContent程序再傳遞給UIProcess程序,直到被我們的代理方法響應。因此didFinishNavigation在NSURLSession的網路載入結束時就會觸發,但因為跨了兩次程序通訊,因此對比網路層,實際上是有一定的延遲的。與子資源載入和頁面上屏無時間先後關係。

5. Tips

  • 一定要緊密結合三大程序去理解WebKit原始碼,形成基於程序的知識體系。

  • 可以直接修改原始碼驗證猜想。例如在驗證觸發渲染條件時,可以在原始碼中禁止網路層didfinish事件執行,並自己構造資料返回,驗證各類上屏觸發條件。

6. 結語

以上是本文梳理的WKWebView載入生命週期與代理方法剖析。因篇幅有限,本文沒有直接把大量的原始碼放上來逐行解釋,很多網路載入的策略細節也沒有呈現出來,但相信讀者通過本文的整體框架分析和原始碼追蹤思路,結合WebKit原始碼自己除錯,一定能瞭解更多自己感興趣的細節功能的原理,形成自己的知識體系。後面我們將繼續從獨特的視角分析WKWebView裡那些有趣的功能細節,希望總有一塊知識能幫助大家最終將好的想法落實在業務裡,助力實際開發。

敬請期待

深入理解 WKWebView(基礎篇)-- 聊聊 cookie 管理那些事

深入理解 WKWebView(基礎篇)-- 探究 WebKit 網路資源快取

參考連結

WebKit 原始碼:https://github.com/WebKit/WebKit

WebKit 官網:https://webkit.org/

https://jishuin.proginn.com/p/763bfbd60147

------------------越是喧囂的世界,越需要寧靜的思考------------------ 合抱之木,生於毫末;九層之臺,起於壘土;千里之行,始於足下。 積土成山,風雨興焉;積水成淵,蛟龍生焉;積善成德,而神明自得,聖心備焉。故不積跬步,無以至千里;不積小流,無以成江海。騏驥一躍,不能十步;駑馬十駕,功在不捨。鍥而舍之,朽木不折;鍥而不捨,金石可鏤。蚓無爪牙之利,筋骨之強,上食埃土,下飲黃泉,用心一也。蟹六跪而二螯,非蛇鱔之穴無可寄託者,用心躁也。