共计 10053 个字符,预计需要花费 26 分钟才能阅读完成。
相熟 iOS/macOS Hybrid 混合开发的同学应该都有领会,WKWebView 尽管是苹果作为代替 UIWebView\WebView 而推出的 ” 新 ” 组件,但大部分开发者对它切实“爱不起来”。毕竟对于国内大部分利用开发者来说,在理论应用中 WKWebView 所谓的“劣势”未必能体现进去,但带来的“坑”却都着实都不浅。
目前社区或线上可查找的 WKWebView 相干材料,大多比拟古老且随声附和、复制粘贴类的居多。少部分实在实际和摸索的开发者,或者也因工夫或精力的起因,对问题和解决方案未能做具体的论述。导致目前线上 WKWebView 相干的材料数量不少、但品质不高;且有不少文章存在对问题的背景解释不清,解决方案不足无效验证等问题。
我从事端容器畛域开发多年,曾在生产环境方案设计上与 WKWebView “ 反抗 ” 屡次。目前混合开发曾经是古代 App 标配,一方面是对这么长时间用法教训上的总结,另外一方面也心愿可能为还在抗争中的同学提供一些新视角或者解决思路,故筹备联合 WebKit 局部源码,将本人对这个组件的了解以及局部问题解决方案整顿分享一下。本文尝试阐明 3 件事件:
- WKWebView 应用中的典型问题有哪些?
- 为什么会呈现这些问题?
- 这些问题的解决办法有哪些?
根底回顾
iOS 端网络设计和 WKWebView 设计特点咱们能够通过官网材料来查阅。但为了前面更好的阐明问题,上面咱们重点回顾下与文章后续内容相干的两个根本知识点:
- iOS 端网络设计与 Cookie 治理
- WKWebView 多过程模型
iOS 网络设计与 Cookie 治理
Cookie 治理是做混合开发过程中常常会波及到的局部,在利用开发中咱们晓得能够通过 NSHTTPCookie 和 NSHTTPCookieStorage 来治理利用的 Cookie。但在零碎层面 Cookie 是如何治理的、如何与网络层各模块进行联动,这对咱们前面剖析 WKWebView 中的 Cookie 问题有着至关重要的分割。
依据官网材料,咱们可知 iOS 平台下网络相干模块大略关系如下:
从上至下模块顺次为:
- WebKit:应用层,客户端 App 以及 WKWebView 处于这一层;
- NSURL:能够了解为对底层 CF 接口的封装扩大层,NSURLConnection、NSURLSession 等处于这一层;
- CFNetwork:iOS 网络模块外围实现层,是网络层设计中最重要的局部。负责网络协议组装发送接管等次要工作,与 CoreFoundation 框架关系严密;
- BSD socket:基于底层硬件接口的 socket 服务。
CFNetwork 是整个网络系统的外围模块,负责组装申请、解决响应等:
核心内容蕴含:
- CFURLRequest:包含 URL/header/body 这些申请的信息。CFURLRequest 会进一步转换成 CFHTTPMessage;
- CFHTTPMessage:次要是 HTTP 协定的定义和转换,把每一个申请 request 转换成规范的 HTTP 格局的文本;
- CFURLConnection:次要是解决申请工作。包含 pthread 线程、CFRunloop、申请队列的治理等等。提供了 start、cancel 等等操作的 API;
- CFHost:负责 DNS,有 CFHostStartInfoResolution 等函数,基于 dns_async_start 和 getaddrinfo_async_start 等办法;
- CFURLCache/CFURLCredential/CFHTTPCookie:解决 缓存 / 证书 /cookie 相干的逻辑,都有对应的 NS 类。
从下面剖析可知要害信息:iOS Cookie 治理相干模块处于 CFNetwork 这一层中。即对于申请 Response 中的 “set-cookie” 字段,在 CFNetwork 中被生产和解决。
WKWebView 多过程模型
通过官网材料,咱们晓得 WKWebView 相比 UIWebView 很大的一个变动是 ” 多过程模型 ”:
WKWebView 在运行时,外围模块运行在独立过程中,与 App 过程独立。
WKWebView 应用中各种问题的起因,有不少和多过程运行模式有很大的关系。
多过程模型详解
但具体是什么样状态的多过程?咱们通过一张简图来阐明下:
- WKWebView(WebKit) 蕴含 3 种过程:UI Process, Networking Process, WebContent Process;
- UI Process:即 App 过程,WKWebView(WebKit) 中局部模块运行在此过程,会负责启动其它过程;
- Networking Process:即网络模块过程,次要负责 WKWebView 中网络申请相干性能;此过程 App 中只会有启动一次,多个 WKWebView 间共享;
- WebContent Process:即 Web 模块过程,次要负责 WebCore, JSCore 相干模块的运行,是 WKWebView 的外围过程。此过程在 App 中会启动屡次,每个 WKWebView 会有本人独立的 WebContent 过程;
- 各个过程之间通过 CoreIPC 过程通信。
总的来说:在一个客户端 App 中,多个 WKWebView 应用中会共享一个 UI 过程 (与 App 过程共享)、共享一个 Networking 过程、每个 WKWebView 实例独享一个 WebContent 过程。
示例:
此处对于 WebContent Process 和 Networking Process 的启动规定,官网文档并未解释特地分明,且因为版本迭代等起因,文档与目前最新规定也略有出入。为防止混同与歧义,上面联合 WebKit 源码稍作剖析。
WebContent 过程启动规定
依据官网文档形容:
A WKProcessPool object represents a single process that WebKit uses to manage web content. To provide a more secure and stable experience, WebKit renders the content of web views in separate processes, rather than in your app’s process space. By default, WebKit gives each web view its own process space until it reaches an implementation-defined process limit. After that, web views with the same WKProcessPool object share the same web content process.
规定是优先应用创立新过程,当过程上线超过某阈值之后则会共享,在 WebKit 外部由 maximumProcessCount 管制。然而此规定只对 iOS13 之前的零碎失效,iOS13 之后的零碎,WKWebView 每次创立实例都会启动一个新的 WebContent Porcess。相干实现如下。
iOS13 前:
iOS 13 及当前:
Networking 过程启动规定
Networking 规定绝对简略,确保在 App 生命周期内启动一例 (Crash 之后会从新创立)。相干代码:
次要问题与解决方案
WKWebView 在生产环境应用中,除去绝对简略的应用和适配问题外,容易对开发者和前端同学造成困扰的问题有 4 个:
- 申请代理问题
- Cookie 治理问题
- 全面屏适配问题
- WebContent 过程解体问题
上面别离对这 4 例问题产生的起因、可尝试的解决方案以及不同计划下引入的问题做一下阐明。
申请代理问题
这一点应该是妨碍 WKWebView 铺开的首要问题。问题背景也绝对简略,并非有什么技术实现上的难度,而是苹果官网不心愿 WKWebView 申请被利用拦挡,美其名曰 ” 为了平安 ”。但在理论应用场景中,咱们又须要对 WebView 的申请进行代理以满足业务和性能诉求,典型场景如:离线包、流量监控等。
官网不反对、业务上又有应用场景,咱们只能尝试通过 ” 黑魔法 ” 来解决。目前实用面比拟多的解决方案有两个:
- 通过 [WKBrowsingContextController registerSchemeForCustomProtocol:] 来注册代理,为不便简称为代理计划 1;
- 通过 [WKWebViewConfiguration setURLSchemeHandler:forURLScheme:] 来注册,为不便简称为代理计划 2。
目前两种解决方案的实现办法都有较为丰盛的材料和阐明,在此不在赘述。
尽管这两种计划某种程度上能够 ” 局部解决问题 ”,但带来的副作用绝对也不少,在生产环境中如何取舍还需具体开发同学来取舍。上面别离通过 “ 代理计划 1 ” 和 “ 代理计划 2 ” 代指来简略阐明下,能够供大家选型时做一个参考。
代理计划 1
此为最早呈现的 WKWebView 申请代理计划,可满足 iOS 9 及当前利用应用(目前最新为 iOS 14)。依据之前调研剖析,业界大部分有代理需要的 App 采纳此计划或变种计划。
1)计划思路
通过 WKBrowsingContextController 将 http(s) 注册到 Networking 的 m_registeredSchemes 数组中。对于数组中的 Scheme,WebKit 在发动申请时会通过 WKCustomProtocol 将申请发送到 App 所在的过程,并由 App 过程来执行发送。
因为从 Networking 过程将数据发送到 App 过程时,WebKit 内无意剥离了 Body 局部 (见 WebCoreArgumentCodersMac.mm):
故须要对携带 body 的申请做一些非凡解决。解决计划是通过在 WKWebView 内注入脚本,重写掉 WebView 内申请发送相干办法。在申请发送之前将 body 局部序列化之后通过 bridge 传递到 App 过程暂存。
App 过程代理 WKWebView 申请时,依据规定按需拼接缓存的 body,实现之后再进行发送动作。
2)计划弊病
此计划尽管实用面较广,然而弊病也很显著。次要有两方面:
(1)问题一:无奈定向解决、只能一刀切
即如果 App 采纳此计划,对其所有 WKWebView 实例的发送的申请都须要代理。如果有 WKWebView 实例中并未注入脚本或者执行代理,则可能导致申请无奈发送、发送短少 body 等问题,常见于一些集成的二方库、三方库中的 WKWebView 实例。
(2)问题二:重写脚本齐备性很难保障
因为须要在 JS 层重写申请发送逻辑,比方 form 表单提交、AJAX、Fetch 等接口,重写接口的品质间接决定计划的齐备度。且 WKWebView 原设计有不少能力在 c++ 层面实现,仅在 JS 重写无奈保障对齐。目前已知的问题有:
- 对于同步申请,此计划目前无奈反对;
- 对于流式申请,比方上传场景,目前反对度较差。只能在 JS 侧全量读取之后再进行发送;
- 无奈解决 Fetch API Stream 返回值;
- 当应用 Form 表单提交内容蕴含大块数据时,可能呈现失落、Crash 等状况。
代理计划 2
此计划是基于苹果在 iOS 11 上凋谢的 [WKWebViewConfiguration setURLSchemeHandler:forURLScheme:] 接口做 ” 扩大 ” 来实现。对于 iOS 11.3 当前的设施,此计划具备较好实用性(WebKit 解决了局部 Body 传递问题)。
1)计划思路
- [WKWebViewConfiguration setURLSchemeHandler:forURLScheme:] 能够在 WKWebView 实例上注册自定义申请 Scheme。如果 WKWebView 发送的申请匹配注册 Scheme,则会代理到 UI 过程 (App 过程) 执行发送动作;
- WKWebView 外部默认不反对注册 http(s) 等规范 Scheme,然而有 ” 黑魔法 ” 可绕过限度;
- 对于 AJAX 发送 BLOB 数据时,也会呈现 body 失落的状况,能够参考 代理计划 1 中相似的计划来解决。
2)计划劣势
代理计划 2 绝对计划 1 两个微小的劣势在于:
- 不必一刀切,配置与 WKWebView 实例绑定:即能够定向解决咱们须要解决的 WKWebView 实例,对于 三方库 中的对象,齐全能够做到无影响,安全性大大提高;
- 不必重写所有发送申请:大部分状况下申请中的 body 是能够被携带到 App 过程,即咱们只需定向解决局部异样即可,健壮性大大晋升。
3)计划弊病
此计划除去有 iOS 11.3 的零碎版本限度外,在具体运行中也有也有不少很难解决的问题,次要如下:
(1)问题一:多图分片下载状况下,WKWebView 外部存在解决时序存在 BUG
问题体现:在 WKWebView 中加载大图、且大图数据存在存在分片返回时,WKWebView 外部时序解决异样可能导致 图片无奈展现、图片展现不残缺等问题。具体可联合 WebKit 中对图片加载的流程来简略阐明下:
问题即呈现在上述 step1, step2, step3 的执行程序上。在异常情况下,会偶现执行程序为:step1 -> step3 -> step2, 且 step3 不再被触发(allDataReceived),进而导致图片最终的内容未渲染上屏。
解决方案:目前暂无无效的解决办法,通过配置 suppressesIncrementalRendering 配置为 YES 某种程度上能够缓解问题,但并无奈根治且对体验略有影响。
(2)问题二:iOS 12 及以下零碎零碎同步 AJAX 导致 Crash
问题体现:在 WKWebView 中如果呈现 Web 页面发送 sync request,则可导致 WebContent 过程解体,WKWebView 回调 webViewWebContentProcessDidTerminate,进而导致页面白屏等问题。此问题可明确是 WebKit 外部的 BUG,且已有相干 Fix:
Bug1: WebURLSchemeHandlerProxy::loadSynchronously crash with sync request(2018-08-06 14:14):https://bugs.webkit.org/show_…
Bug2: WKURLSchemeHandler crashes when sent errors with sync XHR(2019-06-20 01:20):https://bugs.webkit.org/show_…
解决计划:对于 Bug1,解决绝对比拟简答,即在网络申请回调 error 之前优先回调局部空数据,躲避掉问题;然而对于 Bug2,目前短少无效的解决办法。
(3)问题三:301 申请下 SWAP 导致页面无奈转场问题
问题体现:如果页面中存在应用 301 做重定向的状况,可能会呈现重定向页面无奈加载的状况,进而导致页面异样、白屏等问题。
解决计划:敞开 processSwapsOnNavigation,将置为 NO(外部属性)。
总的来说,尽管代理计划 2 相比代理计划 1 有很大的劣势,但此计划因为版本限度等起因,目前使用量相比代理计划 1 略低。且与代理计划 1 相比,此计划的劣势和劣势都很显著,如多图分片场景下可能导致图片无奈展现的问题,目前未找到无效的解决办法。计划 2 是否可代替计划 1 在生产环境应用,还需应用同学本人斟酌。
Cookie 问题
依据官网文档及材料,咱们可知 WKWebView 因为其 ” 独立存储 ”,导致 Cookie 和 Cache 与 App 不互通,进而有问题。然而这种表述较为含糊,且理论应用中 WKWebView 与 App 的 Cookie 并非齐全隔离,这种不置可否的体现很难让人搞清楚 ” 通 ” 或 ” 不通 ” 的边界在哪里。
上面首先依据本人对这块的了解,尝试阐明下 WKWebView 应用 Cookie 问题到底是什么、以及背地的起因。因为苹果并未对全副代码开源,下有不少内容是本人的了解和推断,无奈保障完全正确,仅介绍局部思路和判断,供大家在须要时参考。
Cookie 管理策略
依据上节的背景介绍咱们可知,iOS Cookie 相干内容,是在 CFNetwork 这一层由 CFHTTPCookie、CFHTTPCookieStorage 等来治理,是 CFNetwork 模块的一部分。且对于 Session Cookie 和 长久化 Cookie,零碎有着不同的管理策略:
- Session Cookie:内存中保留,过程周期内失效。在 iOS 挪动端,一个 App 过程 即对应于一个 Session,即 Session Cookie 可在过程内共享;
- 长久化 Cookie:这部分 Cookie 除保留内存以外,还会长久化到磁盘,可屡次应用。本地文件存储在 沙箱文件夹 /Library/Cookies/Cookies.binarycookies;须要特地留神的是:长久化 Cookie 并非在产生之后立刻同步到 Cookies.binarycookies,依据教训会有一个 300ms ~ 3s 的提早。
WKWebView Cookie 问题
基于上节的 iOS Cookie 治理、联合多过程模型,咱们大略能够推断 App 与 WKWebView Cookie 治理模型,见如下简图:
留神:WKHTTPCookieStore 为示意,画到了 Networking 过程,理论状况中此模块扩散在 WebContent、Networking 以及 UI Process 中,且各过程中的局部通过 IPC 桥接。
依据上图能够疏导出 WKWebView Cookie 相干的 2 个外围点:
1)WKWebView Cookie 问题具体是什么
- 对于 “Session Cookie”:App 过程与 WKWebView 过程(WebContent + Networking)之间 齐全隔离;
- 对于 “ 长久化 Cookie”:App 过程与 WKWebView 过程(WebContent + Networking)之间 同步存在时差。
2)造成 WKWebView Cookie 问题的根本原因
- App 过程 与 Networking 双过程的设计。
外围指标
在理解 WKWebView 问题以及对应的根本原因之后,如何来解决此问题绝对也清晰了:依据是否采纳代理了 WKWebView 的网络申请,咱们须要不同的解决策略。
- 场景 1 – 未代理 WKWebView 网络申请:Cookie 齐全由 Networking 过程治理,WKWebView 内可自闭环。大部分状况下 App 过程也无需感知,如果的确须要感知,能够依据业务场景抉择 JS 桥接、强制长久化等计划;
- 场景 2 – 已代理 WKWebView 网络申请:Cookie 大部分是由 App 过程来治理,此时应该采纳何种同步策略。
因为场景 1 中咱们并未在生产环境中采纳,故本文不打算做冒然剖析。上面次要聚焦于场景 2 来做进一部分剖析。在场景 2 下咱们的外围指标:
- 对于 App 过程中产生的 Cookie,可能及时同步到 Networking 过程:次要解决 Reponse 中存在 “Set-Cookie” 状况下,JS 端如何及时读取相干 Cookie 的问题;
- 对于 WebContent 中由 JS 产生的 Cookie,能及时同步到 App 过程:次要解决在 JS 端产生 Cookie 之后,咱们如何保障在后续代理的网络申请中可被失常携带的问题。
同步伎俩
在确认计划之前,咱们首先要搞清楚一个问题:客户端侧 Cookie 起源有哪些?
对于 App 过程,Cookie 起源有两个:
- 通过 NSHTTPCookieStorage 写入的;
- 在网络申请 Response Header 中通过 “Set-Cookie” 写入的。
对于 WebContent 过程,次要是 JS 通过 document.cookie 写入的(网络代理之后 Set-Cookie 不会在 WKWebView 过程中失效)。
其次,咱们要确认可用做同步的伎俩有哪些:
对于 iOS 11 之后的零碎,苹果曾经为咱们提供了 WKHTTPCookieStore 对象用来读写、监听 WKWebView 对应的 Cookie,能够间接应用。
对于 iOS 11 之前的零碎,须要辨别解决一下。
从 App 进程同步到 Networking 过程,简略流程如下:
- 第 1 步,须要把 Session Cookie 长久化,长期保留(留神须要标识,以供复原);
<!—->
- 第 2 步,调用 NSHTTPCookieStorage 外部接口 _saveCookies 触发强制同步;
<!—->
- 第 3 步,复原长期保留的 Session Cookie,防止净化。
因为 Networking 过程不会产生 Cookie,所以咱们上面要做的是从 WebContent 进程同步 Cookie:解决策略即在 JS 偏重写 document.cookie 办法,在 JS 批改 cookies 时,通过 bridge 将 cookie 传递到 App 过程。
解决计划
无理分明问题、指标和可用伎俩之后,咱们即可总结出 WKWebView Cookie 相干问题的解决计划:
- 对于 iOS 11 及之后的零碎,咱们能够通过 HOOK NSHTTPCookieStorage 中读写 Cookie 的接口,并且监听网络申请中 “Set-Cookie” 关键字,在 App 过程 Cookie 发生变化时同步到 WKWebView;同时通过 WKHTTPCookieStore 提供 cookiesDidChangeInCookieStore 能力来监听 WKWebView 中 Cookie 的变动;
- 对于 iOS 11 之前的零碎,解决策略相似。然而咱们须要过 NSHTTPCookieStorage 接口来做强制同步,并且须要留神复原 Cookie 的 SessionOnly 属性;同时须要通过在 JS 偏重写 document.cookie 的形式,来感知 WKWebView 中 Cookie 的变动。
特地留神:
采纳 iOS 11 之后计划解决时肯定要留神,对 WKHTTPCookieStore 的操作会 波及到 IPC 通信,如果通信过于频繁、通信数据量过大,会产生显著的性能问题。极其状况可能造成 IPC 模块异样,呈现所有 WKWebView 都无奈加载的状况。比方典型的场景,如果一个页面申请较多、每个申请都带 ”Set-Cookie”、且业务上为了简略,每次把 App 过程的 Cookie 全量同步到 WKWebView,则 Cookie 过多时,有肯定概率(暴力测试可复现)触发 IPC 异样,导致后续所有 WKWebView 实例都无奈失常加载,只有 App 杀过程才可复原。倡议在同步 Cookie 时,尽量按需同步变动的局部。
全面屏适配问题
全面屏适配问题绝对不简单,但因为 WKWebView 如 UIWebView 在体现上的不同,导致容易造成一些困扰。
问题是 UIWebView 与 WKWebView 在对前端 viewport-fit 反对体现上略有差异:UIWebView 对 viewport-fit 反对度较好,体现根本与官网文档表述统一。然而 WKWebView 中存在一个潜规则,如果 Web 页面内 body 的高度,在没有超出 WKWebView 组件理论高度时,viewport-fit=cover 可能不失效。
解决方法是在页面中躲避掉此类情况,如配置 body height 为 100vh (或其它相似计划)。
WebContent 过程解体问题
这是一个呈现概率不高,然而不足通用、无效解决办法的问题。咱们晓得 WKWebView 多过程模式下,如果 WebContent 过程因为各种起因呈现 Crash,则 WKWebView 会通过 webViewWebContentProcessDidTerminate 回调通知开发者,个别状况下咱们会通过 reload 办法从新加载页面。同时如果用户设施内存缓和,则可能呈现零碎被动 KILL WebContent 过程的状况。即可能呈现 App 过程(前台)失常,然而 WebContent 解体、页面从新加载的状况。
绝大部分状况,进入此流程并不一定会对用户操作造成困扰。然而,如果此时造成内存缓和是因为前端触发业务导致,典型如表单中唤起相机上传图片,此流程对用户的影响可能是致命的。即便咱们通过 WebView reload 使页面复原,用户在执行的上传动作也会被打断,导致提交流程出现异常、影响用户的操作。且如果用户设施进入此状态,大部分状况下用户再次操作还会触发同样的流程。
这种状况下,用户无奈及时感知到造成问题的根本原因,绝大多数直观反馈即为:“App 呈现 bug 了!”故从用户角度来看,短少主动复原、解决问题的方法。
目前此问题不足无效、对立解决办法,一种解决思路是客户端与前端配置,针对外围、可能出现异常的流程,定向设计解决方案。通过端侧的能力来将数据长久化,在相似异样产生之后应用长久化数据恢复现场,尽量在用户无感的状况下保障用户操作流程失常。
总结
以上便是咱们在端容器设计开发过程中,WKWebView 应用上遇到的一些典型问题和对应的解决办法。总的来说,目前造成这么不协调的状态,大部分是因为零碎平台未能充分考虑开发者诉求、组件设计对历史业务兼容性不佳导致的。当然,当初这种状态必然也不是一种正当状态,将来无论是零碎平台方、还是业务方、或者是开发者,当矛盾无奈协调时总有一方要进行斗争。在这个工夫点降临之前,心愿下面总结内容,可能为受此类问题困扰的同学提供一些帮忙。
关注咱们,每周 3 篇挪动干货 & 实际给你思考!