一、前言

  • 从 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加载的生命周期与代理方法相关推荐

  1. asp.net C#母版页和内容页事件排版加载顺序生命周期

    asp.net C#母版页和内容页事件排版加载顺序生命周期 关于ASP页面Page_Load发生在事件之前而导致的问题已经喜闻乐见,对于问题的解释也很全面,但是如何解决问题则较少有人说明,我就再 简单 ...

  2. Fragment的懒加载与生命周期详解

    提示:本文仅为笔者学习记录 Fragment的懒加载与生命周期详解 什么是懒加载 了解Fragment的生命周期 onAttach onCreate onCreateView onActivityCr ...

  3. iOS APP内置WKWebView加载网页获取位置权限弹框文字是英文

    使用WKWebView加载网页,网页上获取位置权限.选择相册弹出框显示英文,如图: 网页选择相册 网页获取当前位置 出现英文是因为项目没有本地化,只需要在info.plist里面添加Localized ...

  4. iOS开发之控制器创建与加载(生命周期)

    1.如何创建一个控制器 控制器常见的创建方式有以下几种: (1)通过storyboard创建 (2)直接创建 MJViewController *mj = [[MJViewController all ...

  5. java 类加载生命周期_Java类的加载与生命周期

    一.概要: 类的生命周期从类的 加载.连接.初始化 开始,到类的 卸载结束: 二.几个阶段: 加载:查找并加载类的二进制数据.(把类的.class文件的二进制数据读入内存,存放在运行时数据区的方法区: ...

  6. java 单例 生命周期_Rhythmk 一步一步学 JAVA (13) Spring-2 之Ben懒加载以及生命周期,单例...

    1.定义Demo类: package com.rhythmk.spring; public class User { public void Init () { System.out.println( ...

  7. Glide 4.9源码解析-图片加载流程

    本文Glide源码基于4.9,版本下载地址如下:Glide 4.9 前言 由于Glide源码真的很复杂,因此本文只分析和贴出与图片加载流程相关的功能以及代码.另外本文Glide源码基于4.9,与3.x ...

  8. iOS WKWebView加载本地文件之权威解说

      在实际的iOS开发中,我们有很多的地方需要通过WKWebView加载本地的文件.但是由于WKWebview存在着一些跨域的问题.UIWebView直接加载的方法不能正常使用了.这里就和大家分享一下 ...

  9. IOS 解决WKWebView加载本地html资源文件异常处理

    wkwebView加载本地资源时,有时候无法加载全css等资源文件.导致无线显示.需要做一下特殊处理: WKWebViewConfiguration *config = [[WKWebViewConf ...

最新文章

  1. Python全家福,这些库你认识哪些?
  2. Oracle 启动例程 STARTUP参数说明
  3. python模块(6)-Pandas 简易使用教程
  4. python怎么处理数据标注_在python中将数据标记为敏感
  5. hadoop 学习(1):搭建环境
  6. 漫谈边缘计算(三):5G的好拍档
  7. 中间件和Django缓存
  8. [转]STL(容器)与DEBUGNEW运算符冲突的解决
  9. Nova 操作汇总(限 libvirt 虚机) [Nova Operations Summary]
  10. 以Debug模式启动JBoss
  11. Java线程池在业务中的实践
  12. linux查找文件并显示修改时间,linux下find命令和文件的三种时间
  13. matlab点扩散函数,点扩散函数的一维数值计算及其MATLAB实现
  14. Android 项目中依赖项目、依赖库、依赖module中的jar包(第三方库)
  15. 在IDEA中如何取消打过的断点
  16. 学大伟业 Day 2 培训总结
  17. 《数据时代 2025》报告-2017年版
  18. UEFI与 Legacy BIOS两种启动模式详解
  19. percona-toolkit的安装及简介
  20. 诚之和:波司登羽绒服都上万了 “土味羽绒服”高溢价引争议

热门文章

  1. c#与mysql数据库连接以及.net framework版本修改问题
  2. Java程序员必备 : Java反编译神器——“GUI” 资源分享
  3. WebView三个方法区别(解决乱码问题)
  4. android 声音,同时播放声音Android
  5. 二值图像连通 C语言,二值图像统计连通区域C语言版
  6. activity 点击后传递数据给fragment_Fragment 的过去、现在和将来
  7. 实验11.2 链表 6-1 建立学生信息链表
  8. linux 文件系统 代码,Linux文件系统介绍
  9. ptp精准时间协议_PTP协议时间同步精度测试
  10. 群晖服务器有多少个硬盘,群晖新款NAS发布 采用16个硬盘位、最高支持192TB容量...