Un WebView se vende como «la web, dentro de tu app»: actualizaciones instantáneas, sin pasar por la tienda. En producción es una caché que no controlas, dividida entre dos plataformas, que decide en silencio qué versión de tu página cuenta como «actual». En iOS no hay básicamente offline ni Service Worker. En Android la caché respeta tus encabezados solo a veces. El resultado previsible es una página que renderiza el JavaScript de la semana pasada bajo el HTML de esta: una pantalla en blanco sin error, sin stack y sin causa obvia.

La caché que decide por su cuenta

Cuando publicas una página web firmas un contrato: el HTML referencia un bundle concreto, y ese bundle espera el DOM del HTML. Un navegador mantiene el contrato revalidando en un calendario que tú fijas. Un WebView lo mantiene según el ánimo de la plataforma. La caché está por debajo de tu código, por debajo de tu CDN y por debajo de tu control. Puedes enviar los encabezados correctos; no puedes obligar al motor a obedecerlos.

iOS: sin offline, sin Service Worker

En iOS, WKWebView se construye sobre URLSession y respeta los encabezados HTTP de cache-control estándar, pero esa caché es en memoria y por sesión, así que se evapora en cuanto el proceso muere. No hay caché offline persistente. El respaldo oficial para offline es un Service Worker, y los Service Workers no están disponibles en WKWebView por defecto. Solo funcionan con el permiso privado de Web Browser que los desarrolladores de terceros no pueden obtener, o activando limitsNavigationsToAppBoundDomains y declarando cada dominio en WKAppBoundDomains: tope de diez entradas, sin comodines y sin esquemas de URL personalizados. El antiguo issue de WebKit que rastrea esto (bug 206741) sigue abierto. Para una página embebida, offline simplemente no es algo que la plataforma te dé.

Android: una caché que ignora tus encabezados

Android WebView sí soporta Service Workers, pero su caché HTTP es la trampa mayor. Sin Cache-Control, el motor aplica una heurística: guarda una respuesta durante aproximadamente (Date − Last-Modified) × 10% de su antigüedad, y durante esa ventana ni siquiera toca la red. Peor aún, en dispositivos con el modo LOAD_DEFAULT por defecto, la caché puede ignorar un no-cache del servidor y servir la copia local de todos modos. El mismo equipo que puso max-age=31536000 a un asset estático —porque es «rápido»— construyó una página que no se actualizará en un año en esos dispositivos.

El modo de fallo de pantalla en blanco

Aquí es donde las dos plataformas se encuentran de la peor manera. Publicas un nuevo HTML que apunta a un bundle renombrado o con hash de contenido. El HTML se obtiene fresco, pero el JavaScript o CSS viejo sigue en la opaca caché de disco del WebView. El nuevo DOM carga, el script viejo corre contra él, y el resultado es un layout roto o —a menudo— una pantalla en blanco silenciosa. Los usuarios deben borrar los datos de la app para recuperarse. La conocida historia de guerra de los devs chinos «H5 页面发版后内容未更新» no es un bug de una sola app; es el modelo de caché de la plataforma combatiendo tu release.

La solución del runtime

Un runtime propio del host no apuesta por la caché del navegador. Sirve recursos versionados y deterministas desde la capa del host: el runtime sabe qué HTML pertenece a qué bundle y no dejará que un fragmento obsoleto cargue contra un nuevo shell. El offline se vuelve una capacidad de primera clase gestionada por el host —una caché persistente que el runtime controla—, no una esperanza de que el motor respete tus encabezados. La página que pretendes es la página que renderiza, en ambas plataformas, cada vez.

DimensiónWebViewRuntime propio del host
Offline iOSSin Service Worker; caché en memoria muere con el procesoCaché persistente gestionada por el host
Caché AndroidHeurística (Date−LM)×10%; ignora no-cache en algunos dispositivosFetch determinista, con versión fijada
Visibilidad de actualizaciónJS obsoleto bajo HTML nuevo → pantalla en blancoShell y bundle avanzan juntos
RecuperaciónEl usuario debe borrar datos de la appEl runtime controla la invalidación
MultiplataformaDos bugs de caché distintosUn contrato consistente

El hilo conductor de esta serie es el control. Un WebView entrega a tu pipeline de releases una caché que no posees ni puedes confiar. Un runtime te deja fijar la versión. Para un catálogo de páginas de terceros que transan dentro de tu app, esa diferencia es la brecha entre una página que se actualiza cuando tú lo dices y una que se actualiza cuando el motor se digna.

Fuentes