A WebView is sold as «the web, inside your app» — instant updates, no app-store round trip. In production it is a cache you do not control, split across two platforms, that quietly decides which version of your page counts as «current.» On iOS there is effectively no offline and no Service Worker. On Android the cache honors your headers only sometimes. The predictable outcome is a page that renders last week's JavaScript under this week's HTML: a white screen with no error, no stack, and no obvious cause.
The cache that decides on its own
When you ship a web page you ship a contract: the HTML references a specific bundle, and that bundle expects the HTML's DOM. A browser keeps the contract by revalidating on a schedule you set. A WebView keeps it by whatever the platform feels like. The cache sits below your code, below your CDN, and below your control. You can send the right headers; you cannot make the engine obey them.
iOS: no offline, no Service Worker
On iOS, WKWebView is built on URLSession and respects standard HTTP cache-control headers — but that cache is in-memory and session-scoped, so it evaporates the moment the process dies. There is no persistent offline cache. The official fallback for offline is a Service Worker, and Service Workers are not available in WKWebView by default. They work only with the private Web Browser entitlement that third-party developers cannot obtain, or by enabling limitsNavigationsToAppBoundDomains and declaring every domain in WKAppBoundDomains — capped at ten entries, no wildcards, and no custom URL schemes. The long-standing WebKit issue tracking this (bug 206741) is still open. For an embedded page, offline is simply not something the platform gives you.
Android: a cache that ignores your headers
Android WebView does support Service Workers, but its HTTP cache is the bigger trap. With no Cache-Control set, the engine applies a heuristic: it caches a response for roughly (Date − Last-Modified) × 10% of its age, and during that window it will not even hit the network. Worse, on devices running the default LOAD_DEFAULT mode, the cache can ignore a server's no-cache and serve the local copy anyway. The same team that set max-age=31536000 on a static asset — because it is «fast» — built a page that will not update for a year on those devices.
The white-screen failure mode
This is where the two platforms meet in the worst way. You ship a new HTML that points at a renamed or content-hashed bundle. The HTML is fetched fresh, but the old JavaScript or CSS is still in the WebView's opaque disk cache. The new DOM loads, the old script runs against it, and the result is a broken layout or — often — a silent white screen. Users must clear app data to recover. The well-known Chinese-dev war story «H5 页面发版后内容未更新» is not a bug in one app; it is the platform's cache model fighting your release.
The runtime fix
A host-owned runtime does not gamble on the browser cache. It serves version-pinned, deterministic assets from the host layer: the runtime knows which HTML belongs to which bundle and will not let a stale fragment load against a new shell. Offline becomes a first-class, host-managed capability — a durable cache the runtime controls — not a hope that the engine honors your headers. The page you intend is the page that renders, on both platforms, every time.
| Dimension | WebView | Host-owned runtime |
|---|---|---|
| iOS offline | No Service Worker; in-memory cache dies with process | Host-managed durable cache |
| Android cache | Heuristic (Date−LM)×10%; ignores no-cache on some devices | Version-pinned, deterministic fetch |
| Update visibility | Stale JS under new HTML → white screen | Shell and bundle move together |
| Recovery | User must clear app data | Runtime controls invalidation |
| Cross-platform | Two different cache bugs | One consistent contract |
The through-line of this series is control. A WebView hands your release pipeline a cache it does not own and cannot trust. A runtime lets you pin the version. For a catalog of other people's pages transacting inside your app, that difference is the gap between a page that updates when you say so and a page that updates whenever the engine feels like it.
Sources
- WKWebView has no persistent offline cache — it relies on URLSession's in-memory, session-scoped HTTP cache — root-cause analysis: the cache evaporates when the process dies, and Service Workers are not an option inside the app.
- Service Workers are not supported in WKWebView without App-Bound Domains (capped at 10, no custom schemes) — takazudo's WKWebView gotchas: offline and background sync are unavailable by default.
- WebKit bug 206741 — WKWebView does not implement Service Workers — the long-standing WebKit tracking issue; SW is gated behind an entitlement not available to third-party apps.
- Android WebView default cache heuristic serves stale assets when Cache-Control is missing or ignored — (Date − Last-Modified) × 10% heuristic; some devices ignore
no-cacheunderLOAD_DEFAULT. - AppCache removed: Chrome 90 (Android WebView) and iOS 15 — Service Workers are the only offline path left — and SW does not work inside WKWebView, leaving iOS apps with no offline cache.