WebView 被包装成「网页,装在 App 里」——秒级更新、不用过商店。到了生产环境,它却是个你控制不了的缓存,横跨两个平台各自为政,悄悄决定你的页面哪一版才算「当前」。iOS 上几乎没有离线、也没有 Service Worker;Android 上缓存时不时无视你设的 Cache-Control。可预见的结局是:本周的 HTML 套着上周的 JS 在跑——一个没有报错、没有堆栈的无声白屏。
自己说了算的缓存
你发布一个网页,等于签了一份契约:HTML 引用某个特定 bundle,而 bundle 又依赖 HTML 的 DOM。浏览器按你设定的节奏做再校验来守住这份契约。WebView 却按「平台心情」来守。缓存层躲在你的代码之下、CDN 之下、你的控制之外。你可以发对响应头,却没法逼着引擎照做。
iOS:没有离线,也没有 Service Worker
在 iOS 上,WKWebView 基于 URLSession 构建,遵守标准 HTTP 缓存头——但这个缓存是内存级、会话级的,进程一死就蒸发,没有持久离线缓存。官方给出的离线退路是 Service Worker,而 Service Worker 在 WKWebView 里默认不可用。它要么依赖第三方开发者拿不到的私有 Web Browser 授权,要么得开启 limitsNavigationsToAppBoundDomains 并在 WKAppBoundDomains 里逐个声明域名——上限 10 个、不支持通配符、也不支持自定义 URL scheme。追踪此问题的 WebKit 老 issue(bug 206741)至今未关。对嵌入式页面而言,离线根本不是平台给你的东西。
Android:一套会无视你响应头的缓存
Android WebView 确实支持 Service Worker,但真正的大坑是它的 HTTP 缓存。没有 Cache-Control 时,引擎套用一条启发式规则:把响应缓存大约(Date − Last-Modified)× 10% 的时长,在这段窗口内它连网络都不会打。更糟的是,在默认 LOAD_DEFAULT 模式的机型上,缓存会无视服务端的 no-cache,照样把本地副本甩给你。同一个团队给静态资源设了 max-age=31536000(因为「快」),等于造了一个在这些机型上一年都不更新的页面。
白屏故障模式
这是两个平台在最坏处交汇的地方。你发了新的 HTML,指向一个改名或带内容哈希的 bundle。HTML 被新鲜拉取,可旧的 JS/CSS 还躺在 WebView 不透明的磁盘缓存里。新 DOM 加载上来,旧脚本对着它跑,结果就是布局崩坏——或者更常见的——一声不响的白屏。用户只能清掉 App 数据才能恢复。圈内那句耳熟能详的「H5 页面发版后内容未更新」,不是某一家 App 的 bug,而是平台的缓存模型在跟你的发布对着干。
runtime 解法
宿主自有 runtime 不拿浏览器缓存去赌。它从宿主层下发版本锁定、确定性的资源:runtime 清楚哪个 HTML 配哪个 bundle,绝不让陈旧的片段搭上新外壳。离线变成宿主一手管理的一等能力——一个 runtime 掌控的持久缓存——而不是指望引擎会遵守你的响应头。你想要的页面,就是每次都渲染出来的那个页面,两个平台皆然。
| 维度 | WebView | 宿主自有 runtime |
|---|---|---|
| iOS 离线 | 无 Service Worker;内存缓存随进程消亡 | 宿主托管的持久缓存 |
| Android 缓存 | 启发式(Date−LM)×10%;部分机型无视 no-cache | 版本锁定、确定性拉取 |
| 更新可见性 | 新 HTML 套旧 JS → 白屏 | 外壳与 bundle 同步前进 |
| 故障恢复 | 用户须清 App 数据 | runtime 掌控失效策略 |
| 跨平台 | 两种不同缓存 bug | 同一套一致契约 |
本系列的主线始终是「控制权」。WebView 把一条你不拥有、也无法信任的缓存塞进你的发布流水线。runtime 让你锁定版本。对一套承载别人页面在你的 App 里交易的 catalog 来说,这中间的差距,就是「页面在你说了算时才更新」与「页面在引擎心情好时才更新」的差距。
参考来源
- WKWebView 没有持久离线缓存——它依靠 URLSession 的内存级、会话级 HTTP 缓存 — 根因分析:进程死亡缓存即蒸发,Service Worker 在 App 内不是选项。
- WKWebView 默认不支持 Service Worker,除非开启 App-Bound Domains(上限 10、无通配符、无自定义 scheme) — takazudo 的 WKWebView 避坑清单:离线与会话同步默认不可用。
- WebKit bug 206741——WKWebView 未实现 Service Worker — 长期未关的 WebKit 跟踪 issue;SW 被一道第三方拿不到的授权挡在门外。
- Android WebView 默认缓存启发式在缺少或忽略 Cache-Control 时下发陈旧资源 —(Date − Last-Modified)× 10% 启发式;部分机型在
LOAD_DEFAULT下无视no-cache。 - AppCache 已移除:Chrome 90(Android WebView)与 iOS 15——Service Worker 成为唯一的离线出路 — 而 SW 在 WKWebView 内不工作,iOS App 因此没有任何离线缓存。