Um WebView é vendido como «a web, dentro do seu app»: atualizações instantâneas, sem passar pela loja. Em produção é um cache que você não controla, dividido entre duas plataformas, que decide em silêncio qual versão da sua página conta como «atual». No iOS não há basicamente offline nem Service Worker. No Android o cache respeita seus cabeçalhos só às vezes. O resultado previsível é uma página que renderiza o JavaScript da semana passada sob o HTML desta: uma tela em branco sem erro, sem stack e sem causa óbvia.
O cache que decide por conta própria
Quando você publica uma página web, assina um contrato: o HTML referencia um bundle específico, e esse bundle espera o DOM do HTML. Um navegador mantém o contrato revalidando num cronograma que você define. Um WebView o mantém conforme o humor da plataforma. O cache fica abaixo do seu código, abaixo do seu CDN e abaixo do seu controle. Você pode enviar os cabeçalhos certos; não pode obrigar o motor a obedecer.
iOS: sem offline, sem Service Worker
No iOS, o WKWebView é construído sobre o URLSession e respeita os cabeçalhos HTTP de cache-control padrão, mas esse cache é em memória e por sessão, então evapora assim que o processo morre. Não há cache offline persistente. O recurso oficial para offline é um Service Worker, e os Service Workers não estão disponíveis no WKWebView por padrão. Só funcionam com a permissão privada de Web Browser que desenvolvedores de terceiros não obtêm, ou ativando limitsNavigationsToAppBoundDomains e declarando cada domínio em WKAppBoundDomains: limite de dez entradas, sem curingas e sem esquemas de URL personalizados. O antigo bug do WebKit que rastreia isso (bug 206741) segue aberto. Para uma página embutida, offline simplesmente não é algo que a plataforma lhe dá.
Android: um cache que ignora seus cabeçalhos
O Android WebView sim suporta Service Workers, mas seu cache HTTP é a armadilha maior. Sem Cache-Control, o motor aplica uma heurística: guarda uma resposta por cerca de (Date − Last-Modified) × 10% de sua idade, e nessa janela nem toca a rede. Pior, em dispositivos com o modo LOAD_DEFAULT padrão, o cache pode ignorar um no-cache do servidor e servir a cópia local mesmo assim. O mesmo time que colocou max-age=31536000 num asset estático —porque é «rápido»— construiu uma página que não se atualizará por um ano nesses dispositivos.
O modo de falha de tela em branco
É aqui que as duas plataformas se encontram do pior jeito. Você publica um novo HTML que aponta para um bundle renomeado ou com hash de conteúdo. O HTML é buscado fresco, mas o JavaScript ou CSS velho ainda está na opaca cache de disco do WebView. O novo DOM carrega, o script velho roda contra ele, e o resultado é um layout quebrado ou —muitas vezes— uma tela em branco silenciosa. Os usuários precisam limpar os dados do app para se recuperar. A conhecida história de guerra dos devs chineses «H5 页面发版后内容未更新» não é um bug de um único app; é o modelo de cache da plataforma brigando com seu release.
A solução do runtime
Um runtime próprio do host não aposta no cache do navegador. Ele serve recursos versionados e determinísticos a partir da camada do host: o runtime sabe qual HTML pertence a qual bundle e não deixará um fragmento obsoleto carregar contra um novo shell. O offline vira uma capacidade de primeira classe gerenciada pelo host —um cache persistente que o runtime controla—, não uma esperança de que o motor respeite seus cabeçalhos. A página que você pretende é a página que renderiza, em ambas as plataformas, toda vez.
| Dimensão | WebView | Runtime próprio do host |
|---|---|---|
| Offline iOS | Sem Service Worker; cache em memória morre com o processo | Cache persistente gerenciada pelo host |
| Cache Android | Heurística (Date−LM)×10%; ignora no-cache em alguns dispositivos | Fetch determinístico, com versão fixada |
| Visibilidade de atualização | JS obsoleto sob HTML novo → tela em branco | Shell e bundle avançam juntos |
| Recuperação | Usuário precisa limpar dados do app | O runtime controla a invalidação |
| Multiplataforma | Dois bugs de cache distintos | Um contrato consistente |
O fio condutor desta série é o controle. Um WebView entrega ao seu pipeline de releases um cache que você não possui nem pode confiar. Um runtime deixa você fixar a versão. Para um catálogo de páginas de terceiros que transacionam dentro do seu app, essa diferença é a brecha entre uma página que se atualiza quando você manda e uma que se atualiza quando o motor se lembra.
Fontes
- WKWebView não tem cache offline persistente — depende do cache HTTP em memória e por sessão do URLSession — análise de causa raiz: o cache evapora ao morrer o processo e os Service Workers não são opção dentro do app.
- Service Workers não são suportados no WKWebView sem App-Bound Domains (limite 10, sem curingas, sem esquemas próprios) — as armadilhas do WKWebView do takazudo: offline e sync em segundo plano indisponíveis por padrão.
- WebKit bug 206741 — WKWebView não implementa Service Workers — o antigo issue de rastreamento do WebKit; SW fica atrás de uma permissão indisponível para terceiros.
- A heurística de cache padrão do Android WebView serve assets obsoletos quando falta ou é ignorado o Cache-Control — heurística (Date − Last-Modified) × 10%; alguns dispositivos ignoram
no-cachesobLOAD_DEFAULT. - AppCache removido: Chrome 90 (Android WebView) e iOS 15 — os Service Workers são a única saída offline — e o SW não funciona dentro do WKWebView, deixando apps iOS sem cache offline.