宿主 App 控制不了 WebView 的网络栈。iOS 上,WKWebView 根本无法拦截 HTTPS——Apple 明文规定为 http/https 注册 WKURLSchemeHandler 会抛 NSInvalidArgumentException,而 NSURLProtocol 拦截 http/https 在 WKWebView 中不受支持。Android 的 shouldInterceptRequest 只是「部分」:重定向的后续跳、blob:、javascript: 一概不触发。于是没有宿主强制的证书锁定、没有透明鉴权、没有连接复用、没有请求策略——每个页面重新握手 TLS,数据外泄到未知主机也看不见。宿主自有 runtime 在原生层中介网络:锁定证书、注入签名鉴权、allowlist、统一遥测,页面永远绕不过去。

宿主看不到那根网线

WKWebView 的网络跑在一个独立的网络进程里,Apple 明确禁止覆盖系统自带的 http/https 处理。文档写道:为 WebKit 已经处理的 scheme(例如 https)注册 handler 属于「编程错误」,尝试就会抛 NSInvalidArgumentException。NSURLProtocol 拦截 http/https 在 WKWebView 中不受支持——Apple 自家的开发者关系工程师确认,唯一被认可的路径是自定义 scheme。于是宿主根本无法安插在页面与它的 HTTPS 请求之间。页面要取什么,就走系统自带的 TLS、用系统自带的政策,彻底跳出 App 的掌控。

Android 是「部分」,不是「可控」

Android 上,shouldInterceptRequest(API 21)允许宿主为某个资源返回响应,但它对 javascript:、blob:、android_asset 不触发,重定向时也只有最初那一个资源 URL 会被调用——后续跳转的 URL 一律不回调。即便它确实触发了,也无法在系统自带的 TLS 连接上透明地加请求头,更无法强制证书锁定。这个控制点在 API 文档里看着完整,实践中却漏得厉害:一条重定向链、一段内联 blob、一次脚本发起的 fetch,都会悄悄绕过宿主以为自己已经钩住的东西。

没有可控网络层,你会失去什么

一个宿主无法中介的 WebView,会夺走 App 其余部分视为理所当然的那些控制:

  • 证书锁定。 WebView 唯一的钩子是 onReceivedSslError——一个事后才来的「错误」,你可以选择忽略它——这不是真正的锁定。App 原生 HTTP 客户端里随附的防中间人保护,对 WebView 流量并不生效。
  • 透明鉴权。 你无法给每一个 WebView 请求附上签名请求头或设备证明,除非用一条页面能侦测或拦截的 JS 桥。
  • 连接复用。 每个页面、每个 WebView 都重新握手 TLS;没有宿主级连接池,也没有跨导航的 HTTP/3 0-RTT。
  • 请求策略。 你无法在平台层阻止页面向某个未知主机发请求——数据外泄完全不可见。
  • 遥测。 你无法像度量原生调用那样,统一度量 WebView 的请求延迟、失败与字节数。

runtime 解法

宿主自有 runtime 在原生层、页面之下中介每一次请求。它按已知集合锁定证书并 fail-closed,把签名鉴权与设备证明注入出站请求、页面永远看不见,维护一个宿主级连接池并协商 HTTP/3,再执行 allowlist——未知主机在 socket 打开之前就被拦下。统一遥测随每个请求一同上报。页面看不到也无法绕过这套策略,因为策略活在页面之下、在 runtime 里,而不在一条它能甩开的桥里。

维度WebView宿主自有 runtime
HTTPS 拦截iOS 不可能;Android 部分原生层拦截,完整
证书锁定仅有 onReceivedSslError,可绕过强制锁定集合,fail-closed
透明鉴权仅 JS 桥,可侦测签名头 + 设备证明,不可见
连接复用每页 TLS,无池宿主池 + HTTP/3,0-RTT
请求策略平台层无法拦截allowlist,连接前拦截
遥测无统一视图每请求的延迟/失败/字节

本系列反复回到一个事实:WebView 继承了平台最弱的那些面,还额外加了一个「Web 形状」的面。网络是所有面里最基础的一个——而在原生 WebView 里,它恰恰是宿主最无力治理的那一个。runtime 把 App 重新放回网线上,于是流量是 App 该去守护的,而不是系统替它决定的。

参考来源