iOS之深入解析WKWebView加载的生命周期与代理方法
Posted Forever_wj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS之深入解析WKWebView加载的生命周期与代理方法相关的知识,希望对你有一定的参考价值。
一、前言
- 从 WebView 开始加载一条请求,到页面完整呈现这一过程发生了什么?无论是做 WebView 性能优化还是异常问题监控与排查,都离不开对WKWebView加载的生命周期与代理方法的剖析。
- 在 WebKit 源码的调试基础之上, 结合 ios 端 WKWebView 的 WKNavigationDelegate 代理方法,站在移动端的视角深入分析 WKWebView 网络请求加载的生命周期流程。
- WebKit 源码的调试,请参考我之前的博客:iOS之深入解析WKWebView的WebKit源码调试与分析。
二、 WebKit 加载框架
- 在iOS之深入解析WKWebView的WebKit源码调试与分析验证了 WebKit 三大进程工作模型,并简述了三大进程的主要职责。UIProcess、WebContent、NetworkProces 三大进程间通信关系图如下:
- UIProcess、WebContent、NetworkProces 进程关系分析:
-
- NetworkProcess进程:主要负责网络请求加载,所有的网页共享这一进程。与原生网络请求开发一致,NetworkProcess 也是通过封装的 NSURLSession 发起并管理网络请求的。但不同的是,这一过程中有较多的网络进度的回调工作以及各类网络协议管理,比如资源缓存协议、HSTS 协议、cookie 管理协议等。
-
- WebContent进程:主要负责页面资源的管理,包含前进后退历史,pageCache,页面资源的解析、渲染。并把该进程中的各类事件通过代理方式通知给 UIProcess。
-
- UIProcess进程:主要负责与 WebContent 进行交互,与 APP 在同一进程中,可以进行 WebView 的功能配置,并接收来自 WebContent 进程的各类消息,配合业务代码执行任务的决策,例如是否发起请求,是否接受响应等。
三、WebKit 加载流程
- 使用如下方法,从 UIProcess 层通过 loadReqeust 方法发起页面加载请求(此处 request 只能是 get 请求,如果配置为 post 请求,WebKit 内核基于性能考虑,在跨进程传输时,会将 body 数据丢弃,导致异常):
[self.webView loadRequest:request];
- 通过跟踪 WebKit 源码,我们提取核心步骤如下:
-
- UIProcess 中的 loadRequest 首先会触发 NetworkProcess 进程创建,然后通过进程间通信的方式将 request 发送给 NetworkProcess 进程进行 preconnect 预链接操作,通过网络三次握手建立 TCP 链接,以便加快后续网络资源请求速度。
-
- UIProcess 通过进程间通信的方式将 request 发送给 WebContent 进程,WebContent 进程创建 DocumentLoader 加载器加载网络请求,并取消上个页面的所有还在加载的请求,然后通过字典绑定当前页面ID与创建好的 NetworkProcss 进程(便于服务端数据返回时,查找数据回填所对应的页面),最终将请求交付给 NetworkProcess 中的 NSURLSession 进行处理。
-
- NetworkProcess 通过 NSURLSession 复用之前 preconnect 预链接,继续进行网络加载,此时等待网络请求返回,网络层会继续将数据通过进程间通信方式传输给 WebContent 进程进行处理,开始流式进行数据解析,一边接收一边处理,进行词法分析、语法分析,并在这一过程中加载解析出来的 js、css、图片、字体等子资源,最终动态的生成(DOM 树与 CSSOM 树合成)渲染树,在这一过程中,每次接受到新数据导致渲染树有变更后,就会触发一次 checkAndDispatchDidReachVisuallyNonEmptyState 方法,检查当前页面是否达到上屏状态,若达到上屏状态就进行上屏渲染。
- 达到上屏状态的条件如下:
-
- 如果返回的 data 是普通文本文字,或返回的数据中包含普通文本文字,那只需要达到非空 200 字节即可以触发上屏渲染;
-
- 如果返回的 data 是图片资源类,则判断像素大小 > 32*32,即可触发上屏渲染;
-
- 如果不满足以上条件,对于主文档,判断后面是否继续接收数据,如果不继续,则触发上屏渲染;如后续还有数据,则循环上述流程直至触发上屏。渲染完成,整个加载过程结束。
- WebKit 加载流程如下:
四、WebKit 加载生命周期代理方法
① WKNavigationDelegate 方法
@protocol WKNavigationDelegate <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 方法
- WKNavigationDelegate 代理方法的调用流程如下:
- decidePolicyForNavigationAction 剖析
-
- 如上文的网页加载流程,当 WebContent 即将创建 DocumentLoader 加载器时,会首先触发 decidePolicyForNavigationAction 代理方法。如果我们选择 cancel,那么浏览内核会完全忽略这一操作,后续也不再继续执行其他操作,可以放心的使用 cancel 取消掉我们不想加载的主文档请求,而无需担忧任何异常。
-
- 但当选择 alllow 后,会进入一个稍微复杂的逻辑判断,内核代码首先判断该该链接是否是 universalLink 类型的链接,如果判断是 universalLink 类型的链接,会尝试去调起三方 app,如果能调起,则会 cancel 当前请求,否则才会走到正常的网络加载逻辑(如果需要统计 universalLink 调起情况与或建设屏蔽能力,可以再仔细阅读该处源码)。
- didStartProvisionalNavigation 理解
-
- decidePolicyForNavigationAction 方法中选择 allow 并且判断为非 universalLink 链接后,会立即触发 didStartProvisionalNavigation 方法,表示即将开始加载主文档。这个方法看似只是对 decidePolicyForNavigationAction 方法的确认,但是值得思考的问题是方法名中的 Provisional 究竟是什么意思。
-
- 其实,页面开始页面加载后为了更好的区分加载的各阶段,会将网络加载的初始阶段命名为临时状态,此时的页面是不会记入历史的,直到接收到首个数据包,才会对当前页面进行 committed 提交,并触发didCommitNavigation 方法通知 UIProcess 进程该事件,同时将网络 data 提交给 WebContent 进行渲染树生成。可由此引申出下一个问题,即 didFailProvisionalNavigation 与 didFailNavigation 的关系。
- didFailProvisionalNavigation 与 didFailNavigation 的分别在什么时候执行?它们之间有什么关系?
-
- 当 NetworkProcess 进程发生网络错误时,错误首先由 NSURLSession 回调到 WebContent 层。
-
- WebContent 会判断当前主文档加载状态,如果处于临时态,则错误会回调给 didFailProvisionalNavigation 方法;如果处于提交态,则错误会回调给 didFailNavigation 方法。
- didFinishNavigation 究竟什么时候执行?与页面上屏是否有关?
-
- 在上面的描述中,我们已经理解了 NetworkProcess 层也是使用 NSURLSession 加载主文档的。当 NSURLSession 接收到 finish 事件时,会将该消息通过进程通信方式传递给 WebContent 进程,WebContent 进程再传递给 UIProcess 进程,直到被我们的代理方法响应。
-
- 因此 didFinishNavigation 在 NSURLSession 的网络加载结束时就会触发,但因为跨了两次进程通信,因此对比网络层,实际上是有一定的延迟的。与子资源加载和页面上屏无时间先后关系。
五、Tips
- 一定要紧密结合三大进程去理解 WebKit 源码,形成基于进程的知识体系。
- 可以直接修改源码验证猜想。例如在验证触发渲染条件时,可以在源码中禁止网络层 didfinish 事件执行,并自己构造数据返回,验证各类上屏触发条件。
以上是关于iOS之深入解析WKWebView加载的生命周期与代理方法的主要内容,如果未能解决你的问题,请参考以下文章
iOS之深入解析WKWebView的WebKit源码调试与分析