WKWebView 起動の最適化

Apple が提供する WKWebView はシンプルで使いやすく、一般的なニーズに合わせて数行のコードでコア機能を完成させることができ、開発者に利便性をもたらします。しかし、WKWebView のパフォーマンス向上に関しては、開発者がやるべきことはまだたくさんあると思います。この分野にも興味がある方は、私の意見を聞きに来てください〜

WKWebView 起動最適化.png

1. 背景

ユーザー エクスペリエンス
Web ページを開く速度は、最も直感的なユーザー エクスペリエンスの 1 つです。ただし、Web ページとネイティブ スタートアップの対応する速度にはまだわずかなギャップがあるため、開発者はユーザーにより良いエクスペリエンスを提供するために引き続き懸命に努力する必要があります。

問題の性質を理解することで、問題にうまく対処できるようになります。では、WKWebView の起動が何をしているのかを分析する必要があるのでしょうか?

2.分析

webView の起動を、wkwebView の初期化段階、ブラウザーのネットワーク リソース要求段階、ブラウザーのレンダリング段階の3 つの段階に分けました。次に、それらを1つずつ分析します。

2.1 WKWebView 初期化フェーズ

コードの観点からは、WKWebView の使用は非常に簡単です。

WKWebView *wkWebView = [[WKWebView alloc] initWithFrame:self.view.bounds 構成:構成];

一見問題ないように見えますが、よく考えるとちょっとおかしいと思いますが、なぜおかしいと思いますか?

つまり、alloc が初期化された後、WKWebView が初期化されたということですか?

以下の3点で検討しました。

  1. WKWebView は別のプロセスで実行されています。
  2. WKWebView は NSObject のサブクラスで、 alloc メソッドによって作成された後、実際に APP のプロセス メモリに WKWebView 構造を作成します。
  3. 初期化完了状態が利用可能である必要があります.最初の考慮事項と組み合わせて、WKWebView プロセスも初期化する必要があります。

上記の 3 点を分析すると、alloc 後は初期化が完了したことを意味しないと結論付けることができます。

initメソッドを経て、WKWebViewが初期化されたということですか?

init构造方法确实还不太好从理论上分析,谁也不知道构造方法里面写入了什么内容,好在WebKit开源,那么就直接从源码上面分析吧!

通过源码分析函数走向为: initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration -> _initializeWithConfiguration:(WKWebViewConfiguration *)configuration;

image2022-1-17_17-33-38.png

核心流程在第二个函数中。直接看看部分代码吧~

image2022-1-17_17-41-48.png

从代码上来看,主要做了如下几件事情:

  1. 设置WKProcessPool
  2. 设置配置信息
  3. 初始化WKContentView并绑定
  4. 生成 WebPageProxy并赋值到configuration
  5. 设置窗口大小与展示模式
  6. 添加一些通知
  7. 添加一些代理

总的来看,都是对WKWebView这个对象进行操作,并未提及到进程初始化。所以可以得出结论,init方法之后,也不意味着初始化完成。
那对于WKWebView操作还有两个方法:loadRequest: 和 addSubView:。首先排除addSubView:,直接分析loadRequest:

分析loadRequest:方法。 通过符号断点抓取函数栈,先看看函数栈。

image2022-1-17_17-45-25.png

源码分析WebPageProxy::loadRequest这个方法到底做了什么?

image2022-1-17_17-45-50.png

看1368和1369两行代码,从字面意思描述的比较清楚了。先判断有没有进程,如果没有进程的话,就初始化一个进程~

那先分析hasRunningProcess里面做了什么事情:

image2022-1-17_17-46-25.png

主要就是通过一个m_hasRunningProcess的标识位,来判断当前的WKWebView有没有开启WKWebView的进程。

接下来看看初始化进程lauchProcess方法里面做了什么吧~

image2022-1-17_17-52-16.png

主要操作如下:

  1. 检查器重置。
  2. 843行-844行。WKWebView进程(后面统称WK进程)移除关联的WKWebView与IPC通信。这一步其实很迷惑,既然是WKWebView中没有WK进程的话,那初始化进程的时候为什么会有移除关联的操作? 这里就引伸出来另一个概念:WK进程复用。也就是当我们APP关闭一个WKWebView之后,WK进程并没有立刻结束,而是会在后台保持一段时间。这其实也很好的说明了一个现象:第二次开启WKWebView要比第一次开启WKWebView初始化耗时更少。
  3. 安全启动WK进程。
  4. WK进程绑定当前的WKWebView以及建立IPC通信。
  5. 注入一些信息。

走到这里也可以盖棺定论了,loadRequest:方法之后,才算初始化完成。那总结下WKWebView初始化都经历了那些事情吧,以一张图来展示。

image2022-1-17_17-54-27.png

2.2 浏览器网络资源请求及渲染阶段

一个网页需要完整的在浏览器展示需要用到三种资源:

  1. HTML/SVG/XHTML,HTML字符串描述了一个页面的结构,浏览器会把HTML结构字符串解析转换DOM树形结构。
  2. CSS,解析CSS会产生CSS规则树,它和DOM结构比较像。
  3. Javascript脚本,等到Javascript 脚本文件加载后, 通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree。

而这些资源都是通过网络请求,或者从本地缓存中读取,只有拿到这些资源后,浏览器才能开始进行渲染的操作。简而言之,获取资源的速度决定了WKWebView启动的速度。

那在什么时机才意味着网络资源请求全部完成呢?

所以我们需要分析浏览器下载资源流程,主要流程为四步

  1. 浏览器下载HTML,先从本地缓存进行查找,如果本地缓存没有,则发起网络请求。
  2. 获取HTML后,浏览器解析HTML,然后一边构建DOM,一边下载解析到的CSS,JS资源,也会先从本地缓存进行查找,如果本地缓存没有,则发起网络请求。
  3. 结合DOM与CSS生成的CSSOM,生产Render Tree。这一步是和第2步并发执行。
  4. 浏览器根据Render Tree进行渲染。这一步也是并发执行。

用一张图来表示:

image2022-1-17_17-57-12.png

在HTML解析完成,并下载全部的CSS,JS之后,才能够生成完全的Render Tree,此时的浏览器才能完整的渲染出网页内存。那么在此时,我们才认为网络资源请求已经完成。

3. 解决方案

3.1 四个准则与三个方向

分析完流程,接下来的优化也就比较好解决了,解决方案按照下面四个方向进行就好:

  1. 那些事情可以提前做
  2. 那些事情可以一起做
  3. 那些事情可以不用做
  4. 那些事情可以减少做

我会通过三个方向进行调优:

  1. WKWebView初始化阶段
  2. 浏览器资源请求阶段
  3. 浏览器渲染阶段

3.2 webView初始化阶段调优

  1. 预初始化一个WKWebView (提前做)
  2. WKWebView复用池 (减少做)

流程图如下:

画像.png

需要注意的点:

  1. WKWebView入池后,需要loadRequest一个空页面,此时才能能算真正启动初始化流程。
  2. WKWebView出池时,只有完成didFinish回调后,才能顺利出池。避免因为delegate而引起其他异常。
  3. 复用的WKWebView的需要清除backForwardList。

3.3 浏览器资源请求阶调优

  1. 网络资源缓存池构建 (减少做)
  2. 资源预下载 (提前做)

需要做到上叙四点,需要解决一个壁垒。WKWebView的网络拦截以及WKWebView的资源管理。之前的文章我已经进行了分享,这个方案的特点是:能够完整的拦截到WKWebView发起的网络请求,并且拦截时机是在访问浏览器缓存之前。通过这两个特点,就能很好的对WKWebView网络阶段进行控制和管理。

3.3.1 网络资源缓存池构建

缓存分为两级:

  1. 强缓存:不会向服务器发起请求,直接从缓存中读取缓存。
  2. 协商缓存:当强缓存失败后,携带缓存标识字段向服务器发起请求,由服务器根据缓存标识字段来决定是否使用缓存。

流程图如下:

Web リソース キャッシュ プールを構築する.jpg

3.3.2 资源预下载

通过浏览器网络资源请求的分析,可以得到一个结论:要想知道HTML里面的资源,就得先下载HTML并解析。那除去这种方案,那还有其他方法可以知道HTML里面的资源呢?答案是:前端同学知道。那让前端同学在合适的时机以合适的方式告诉我们就好了。

合适的时机:APP启动60秒之后拉取,以及每10分钟轮询一次。

合适的方式:网页资源表。

资源表结构如下:

image2022-1-17_18-5-44.png

资源分为三个等级:高等级:资源存在资源列表中,且PromptlyDown = 1。高等级资源会在拉取到资源表且为WIFI环境,则立刻下载资源。

中等级:资源存在资源列表中,且PromptlyDown = 0。中等级资源会在拉取到资源表且为WIFI环境且客户端网络有空闲时,则立刻下载资源。

低等级:资源只存在网页列表中。低等级资源只会在打开对应网页时,才会下载资源。
流程图如下:

画像.png

3.4 浏览器渲染阶段调优

  1. HTML预解析 (提前做)
  2. WebView启动流程调优:并发进行webView初始化,HTML下载,资源表CSS,JS下载 (一起做)

3.4.1 HTML预解析

通过原理分析,我们已经知道HTML解析主要包括:生成DOM树,以及加载CSS,JS资源。
那么在将要打开网页时,可以通过资源表中的信息,搜寻到HTML所需的CSS,JS资源。此时可以将所需资源从磁盘加载到内存,亦或是发起网络请求,如此即可模拟HTML解析效果。
流程图如下:

画像.png

3.4.2 WebView启动流程调优

在webView的整体启动流程中,webView初始化、HTML下载,CSS,JS下载三者是一个串行关系。由于HTML,CSS,JS的下载都不是UI操作,所以可在子线程中进行,那么对于这种串行的流程实际上没有完全利用手机性能,造成了性能浪费。通过上叙的优化后,我们可以对webView初始化,以及网络请求进行控制。那么我们就可以将webView初始化、HTML下载,CSS,JS下载三者调整为并发进行,使得性能尽可能的完全使用。

先来看看浏览器本来的资源加载流程:

image2022-1-17_18-6-47.png

改为并发之后为:

image2022-1-17_18-7-6.png

4.总结

4.1 整体流程

image2022-1-17_18-7-53.png

4.2 方案总结

本文从背景开始讲述,简要说明了WKWebView启动优化的理由。
随后分析了WKWebView初始化原理,浏览器获取资源以及渲染原理,正所谓了解问题的本质才能提出最佳方案
最后结合四个准则:提前做,一起做,减少做,不要做。以及三个方向:WKWebView初始化,浏览器资源请求,浏览器渲染。逐个给出阶段性的解决方案,大家可以借鉴参考,有更好的想法也欢迎沟通交流。
通过此次需求,我总结了如下心得:

  1. 总结了四个准则。当遇到速度类优化问题时,不妨先想想这四个准则。
  2. 了解问题的本质。充分了解本质,梳理整体流程,方能给出合理方案。
  3. 阶段性优化。目标太远时,不妨分割成多个优化阶段。不要妄想一口吃成大胖子。
  4. キャッシュをうまく活用してくださいスペースを時間と交換し、デバイスのパフォーマンスを最大限に活用して、より良い体験を得ることができます。
  5. リソース テーブルの魔法。起動速度の最適化に加えて、リソースを動的に追加、削除、および変更することもできます。

関連記事

WebKit の詳細な分析、ブラウザの
レンダリング原理
の詳細な理解、ブラウザのキャッシュ メカニズムの詳細な理解、
URL の入力からページの表示まではどうなるか?

Supongo que te gusta

Origin juejin.im/post/7132788961511997454
Recomendado
Clasificación