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.

DimensionWebViewHost-owned runtime
iOS offlineNo Service Worker; in-memory cache dies with processHost-managed durable cache
Android cacheHeuristic (Date−LM)×10%; ignores no-cache on some devicesVersion-pinned, deterministic fetch
Update visibilityStale JS under new HTML → white screenShell and bundle move together
RecoveryUser must clear app dataRuntime controls invalidation
Cross-platformTwo different cache bugsOne 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