La latencia de navegación es una métrica fundamental para la experiencia del usuario. Para ayudar a los desarrolladores a reducir esta latencia, WebView proporciona APIs para la carga especulativa y la optimización de la conexión, lo que permite que tu app recupere o renderice contenido antes de que el usuario navegue explícitamente a él.
WebView admite tres tipos principales de carga especulativa: Preconnect, Prefetch y Prerender, junto con sugerencias de QUIC para optimizar la negociación de protocolos.
Si implementas una estrategia de carga especulativa, puedes lograr lo siguiente:
- Reducción significativa de la latencia de carga del contenido web: Adelanta la hora de inicio de la red en el ciclo de vida de la app y prioriza protocolos más rápidos, como HTTP/3.
- Mayores tasas de éxito de navegación: Al precalentar la red y la caché, es menos probable que las navegaciones fallen debido a problemas de red transitorios.
- Mejor percepción de la capacidad de respuesta: La renderización previa, en particular, permite transiciones instantáneas que hacen que la app se sienta mucho más rápida.
Elige una estrategia de carga especulativa
El factor diferenciador clave entre estas estrategias radica en su alcance: las APIs de sugerencias de preconexión y QUIC se basan en el origen, lo que significa que solo requieren el dominio de destino. Las APIs de Prefetch y Prerender se basan en URLs, lo que significa que requieren la ruta exacta de la página web.
Dado que las sugerencias de Preconnect y QUIC operan a nivel del origen, puedes iniciarlas mucho antes en el ciclo de vida de la app, incluso antes de que sepas el contenido o la página específicos a los que navegará el usuario.
En la siguiente tabla, se comparan estas tres estrategias para ayudarte a elegir la adecuada para tu caso de uso:
| Función | preconnect | Recuperación previa | Renderización previa |
|---|---|---|---|
| Objetivo principal | Calienta la conexión | Almacenar en caché solo el código HTML (sin JavaScript ni CSS) | Realiza una renderización previa de toda la página |
| Alcance | Nivel de perfil (se comparte en todos los WebView) | Nivel de perfil (se comparte en todos los WebView) | Nivel de WebView (vinculado a un WebView específico) |
| API de Jetpack WebKit | androidx.webkit.Profile |
androidx.webkit.Profile |
androidx.webkit.WebViewCompat |
| Métodos de la API principal | preconnect(...) |
prefetchUrlAsync(...) |
prerenderUrlAsync(...) |
| Configuration | N/A | PrefetchCache.setMaxPrefetches()PrefetchCache.setPrefetchTtlSeconds() |
setMaxPrerenders() |
| Uso de recursos | Baja (red) | Medio (red, memoria) | Alto (CPU, memoria, red) |
| Cuándo debe usarse | Cuando se conoce el origen de destino, pero aún no se determinó la URL específica. | Cuando se conoce la URL exacta y es probable que se navegue, con almacenamiento en caché compartido en WebViews. | Cuando se conoce la URL exacta y la navegación es muy segura dentro de una WebView específica. |
| Beneficios | Configuración de conexión más rápida para cualquier URL en el origen | Carga de red más rápida para las URLs coincidentes | Navegación verdaderamente instantánea al activarse |
Conexión previa a orígenes
La conexión previa acelera las cargas futuras, ya que realiza de forma anticipada búsquedas de DNS y protocolos de enlace TCP/TLS o QUIC para un origen especificado.
A diferencia de Prefetch y Prerender, que requieren una URL de destino exacta, Preconnect se basa estrictamente en el origen. Esto te permite realizar la llamada de Preconnect mucho antes que las de Prefetch y Prerender.
Esta estrategia a nivel del perfil y con pocos recursos reduce la latencia inicial de cualquier uso compartido de WebView que realice el perfil, siempre que no se haya visitado ya el origen. La conexión permanece abierta durante aproximadamente 30 segundos, lo que beneficia a las solicitudes HTTP, las navegaciones o los recursos secundarios de origen cruzado posteriores, ya que elimina la sobrecarga del protocolo de enlace.
Implementación
Para iniciar una preconexión, llama a preconnect(String url) en una instancia de Profile. Se debe llamar a esta API en el subproceso de IU y requiere que se admita WebViewFeature.PRECONNECT.
La API opera en el origen, pero, para mayor comodidad, se puede proporcionar una URL completa (como https://www.example.com/index.html). Esto se trata automáticamente como una llamada al origen (por ejemplo, https://www.example.com). Se pueden conectar varios orígenes llamando a esta API varias veces.
Kotlin
// Must be called on the @UiThread
if (WebViewFeature.isFeatureSupported(WebViewFeature.PRECONNECT)) {
profile.preconnect("https://www.example.com/index.html")
// This initiates a connection to the origin https://www.example.com
}
Java
// Must be called on the @UiThread
if (WebViewFeature.isFeatureSupported(WebViewFeature.PRECONNECT)) {
profile.preconnect("https://www.example.com/index.html");
// This initiates a connection to the origin https://www.example.com
}
Indica la compatibilidad con el protocolo QUIC con sugerencias de QUIC
HTTP/3 (que se ejecuta a través del protocolo de transporte QUIC) ofrece mejoras significativas en la latencia en comparación con HTTP/2, incluidos los protocolos de enlace 0-RTT, una mayor resiliencia de la conexión y la eliminación del bloqueo de línea durante la pérdida de paquetes.
De forma predeterminada, WebView solo intenta una conexión QUIC si tiene una indicación de que el origen admite QUIC, como un encabezado Alt-Svc o un registro HTTPS de DNS de una interacción anterior. Sin este conocimiento previo, WebView usa HTTP/2 o HTTP/1.1 para la conexión inicial.
La llamada a addQuicHints completa previamente esta información de compatibilidad con el protocolo, lo que permite que WebView se conecte con QUIC de inmediato en la primera conexión a los orígenes especificados.
Establece una conexión previa con sugerencias de QUIC
Si bien las sugerencias de Preconnect y QUIC son optimizaciones a nivel del origen en un Profile, cumplen roles distintos y complementarios:
preconnect: Abre y mantiene activamente una conexión de red (búsqueda de DNS y protocolo de enlace TCP/TLS o QUIC) durante aproximadamente 30 segundos. Dado que mantiene abiertas las conexiones de red activas, consume recursos del dispositivo y de la red, y debe reservarse para los orígenes de destino con alta probabilidad.addQuicHints: No genera tráfico de red inmediato. Actualiza las propiedades del servidor en memoria de la pila de redProfilepara registrar la compatibilidad con el protocolo. Dado que tiene una sobrecarga insignificante, puedes configurar de forma segura sugerencias de QUIC durante el inicio de la app para todos los orígenes conocidos compatibles con HTTP/3.
Para obtener un rendimiento óptimo, llama a addQuicHints antes de llamar a preconnect, prefetchUrlAsync o loadUrl. Esto garantiza que cualquier conexión previa o solicitud de página posterior negocie HTTP/3 desde el principio.
Implementación
Para configurar las sugerencias de QUIC, llama a addQuicHints(Set<String> urls) en una instancia de Profile. Debes llamar a esta API en el subproceso de IU y verificar que WebView admita la función WebViewFeature.ADD_QUIC_HINTS_V1.
Al igual que preconnect, addQuicHints opera en orígenes, pero se pueden proporcionar URLs completas (como https://www.example.com/index.html) y se normalizan automáticamente a su origen (https://www.example.com).
El método es aditivo: llamarlo varias veces combina los orígenes proporcionados en todo el Profile.
Kotlin
// Must be called on the @UiThread
@OptIn(Profile.ExperimentalAddQuicHints::class)
if (WebViewFeature.isFeatureSupported(WebViewFeature.ADD_QUIC_HINTS_V1)) {
val quicOrigins = setOf(
"https://www.example.com",
"https://api.example.com"
)
profile.addQuicHints(quicOrigins)
// WebView now prioritizes HTTP/3 over QUIC for connections to these origins
}
Java
// Must be called on the @UiThread
// Requires @Profile.ExperimentalAddQuicHints annotation or suppression
if (WebViewFeature.isFeatureSupported(WebViewFeature.ADD_QUIC_HINTS_V1)) {
Set<String> quicOrigins = new HashSet<>(Arrays.asList(
"https://www.example.com",
"https://api.example.com"
));
profile.addQuicHints(quicOrigins);
// WebView now prioritizes HTTP/3 over QUIC for connections to these origins
}
Configuración común: PrefetchParameters y PrerenderParameters
Tanto la recuperación previa como la renderización previa usan PrefetchParameters o PrerenderParameters para personalizar la solicitud. Estas clases te permiten proporcionar encabezados y sugerencias adicionales para la coincidencia de URLs, como las configuraciones No-Vary-Search.
Kotlin
// Isolated configuration specifically for Cache-Level Prefetching
val prefetchParams = PrefetchParameters.Builder()
.addAdditionalHeader("X-Custom-Client", "Android-App-V2")
.setExpectedNoVarySearchHeader(
NoVarySearchHeader.varyExcept(true, listOf("session_id", "click_ref"))
)
.build()
Java
PrefetchParameters prefetchParams = new PrefetchParameters.Builder()
.addAdditionalHeader("X-Custom-Header", "value")
/**
* Hint to ignore specific query parameters during cache matching.
* This allows the cache to match even if the tracking_id differs.
*/
.setExpectedNoVarySearchHeader(
NoVarySearchHeader.varyExcept(true, Arrays.asList("tracking_id"))
)
/**
* Determines if Client Hints are sent.
* NOTE: This is ignored for Prerendering API requests, which default to
* the WebView's WebSettings.getJavaScriptEnabled() value.
*/
.setJavaScriptEnabled(true)
.build();
Carga previa de contenido
La recuperación previa descarga el recurso HTML principal de una URL y lo almacena en la caché de red del perfil. En WebView, un objeto Profile actúa como contenedor de datos del navegador, incluidas las cookies, la caché HTTP y los service workers. Dado que la recuperación previa es una operación a nivel del perfil, cualquier WebView asociado a ese perfil puede aprovechar la respuesta almacenada en caché.
Implementación
Para iniciar una recuperación previa, llama a prefetchUrlAsync() en una instancia de Profile. Esta operación solo admite el esquema HTTPS.
Kotlin
profile.prefetchUrlAsync(
url,
prefetchParams,
cancellationSignal,
executor,
object : WebViewOutcomeReceiver<PrefetchResult, PrefetchException> {
override fun onResult(result: PrefetchResult) {
if (result.wasDuplicate()) {
// URL and No-Vary-Search permutations already exist in the cache layer
} else {
// The HTML payload has been successfully secured in the HTTP cache
}
}
override fun onError(error: PrefetchException) {
when (error) {
is PrefetchNetworkException -> {
// Isolates network layer or server-side HTTP anomalies
val code = error.httpStatusCode
// Facilitates rapid diagnosis of 4xx or 5xx server responses
}
else -> {
// Catches generalized execution failures and system constraints
}
}
}
}
)
Java
profile.prefetchUrlAsync(
url,
prefetchParams,
cancellationSignal,
executor,
new WebViewOutcomeReceiver<PrefetchResult, PrefetchException>() {
@Override
public void onResult(PrefetchResult result) {
if (result.wasDuplicate()) {
// URL and No-Vary-Search permutations already exist in the cache layer
} else {
// The HTML payload has been successfully secured in the HTTP cache
}
}
@Override
public void onError(PrefetchException error) {
if (error instanceof PrefetchNetworkException) {
// Isolates network layer or server-side HTTP anomalies
int code = ((PrefetchNetworkException) error).httpStatusCode;
// Facilitates rapid diagnosis of 4xx or 5xx server responses
} else {
// Catches generalized execution failures and system constraints
}
}
}
);
Ciclo de vida de la interceptación
La solicitud de recuperación previa de WebView altera cuándo y cómo se activa la devolución de llamada shouldInterceptRequest(). Dado que esto tiene un impacto directo en si el contenido prefetch se usa correctamente, es fundamental comprender el ciclo de vida de dos pasos:
1. La fase especulativa (solicitud de carga previa)
Cuando se invoca prefetchUrlAsync(), WebView descarga el recurso HTML principal en segundo plano. shouldInterceptRequest() se omite por completo para esta solicitud en segundo plano. No se aplica ninguna lógica personalizada, tokens de autorización ni inserciones de encabezado que, por lo general, se controlan dentro de tu interceptor al recurso HTML prefetch.
2. Fase de navegación (activación del usuario)
Cuando la app navega de forma explícita a la URL (por ejemplo, con WebViewCompat.navigate o loadUrl) o el usuario hace clic en un vínculo coincidente, WebView determina si puede usar la caché previa:
Evaluación del HTML principal: En este momento, WebView activará
shouldInterceptRequest()para el HTML principal. Para publicar correctamente la página desde la caché previa a la búsqueda, tu interceptor debe devolvernull. Si devuelves unWebResourceResponsepersonalizado, WebView respeta tu interceptor y omite por completo la caché de recuperación previa.Evaluación de subrecursos: Después de que se autoriza el uso del código HTML prefetch,
shouldInterceptRequest()se activa normalmente para todos los subrecursos posteriores (como imágenes, secuencias de comandos y CSS) necesarios para terminar de renderizar la página.
Comportamientos clave
Las siguientes características operativas y verificaciones de elegibilidad rigen la forma en que WebView inicia y administra las solicitudes de carga previa:
- Seguridad de subprocesos: Las solicitudes se pueden iniciar desde cualquier subproceso.
- Elegibilidad: Antes de iniciar una recuperación, WebView se asegura de que la solicitud sea segura y adecuada para el contexto verificando lo siguiente:
- Cookies existentes: Para proteger la privacidad del usuario y evitar efectos secundarios similares a los de CSRF, es posible que WebView omita la recuperación previa si la solicitud requiere cookies autenticadas específicas que podrían activar un cambio de estado en el servidor.
- Presencia de Service Worker: Si un Service Worker ya controla el alcance de la URL, WebView puede diferir al controlador de recuperación del Service Worker en lugar de iniciar una carga previa de red estándar.
- Disponibilidad del proxy: WebView verifica que la ruta de red actual (incluidos los proxies configurados) sea estable para evitar errores en las solicitudes especulativas en configuraciones de red complejas.
- Si una recuperación previa no se inicia (incluso con parámetros válidos), suele deberse a que WebView determinó que una solicitud en segundo plano podría interferir en la sesión actual del usuario o en su estado de seguridad.
- Cancelación: Usa
CancellationSignalpara finalizar una solicitud en curso y evitar que se almacene en caché.
Renderización previa de páginas
La renderización previa crea "contenido web" oculto para renderizar por completo una página en segundo plano, lo que incluye la ejecución de secuencias de comandos y la recuperación de recursos secundarios. La renderización previa se basa en la misma infraestructura subyacente que la recuperación previa. Si una app inicia una renderización previa, WebView primero realiza una recuperación previa de la respuesta para publicar la navegación de renderización previa, lo que evita la actividad de red redundante.
Implementación
El procesamiento previo es una operación a nivel de la instancia de WebView. Llama a prerenderUrlAsync() con WebViewCompat desde el subproceso de IU.
Kotlin
WebViewCompat.prerenderUrlAsync(
webView,
url,
cancellationSignal,
executor,
params,
object : PrerenderOperationCallback {
override fun onPrerenderActivated() {
// Called when the user navigates to the URL and the hidden page is swapped in
}
override fun onError(exception: Throwable) {
// exception is an instance of PrerenderException
// Handle prerender failure (for example, memory pressure or disallowed JavaScript APIs)
}
}
)
Java
WebViewCompat.prerenderUrlAsync(webView, url, cancellationSignal, executor, params, new PrerenderOperationCallback() {
@Override
public void onPrerenderActivated() {
// Called when the user navigates to the URL and the hidden page is swapped in.
}
@Override
public void onError(@NonNull Throwable exception) {
// Handle prerender failure (for example, resource constraints or disallowed APIs).
}
});
Tanto la recuperación previa como la renderización previa son completamente asíncronas. Se puede llamar a prefetchUrlAsync() desde cualquier subproceso, mientras que prerenderUrlAsync() se debe iniciar desde el subproceso de IU.
Restricciones técnicas
Para equilibrar la navegación instantánea con el estado del sistema, WebView aplica las siguientes restricciones de tiempo de ejecución:
- Presión de memoria: WebView cancela las URLs renderizadas previamente si el dispositivo tiene poca RAM.
- APIs no permitidas: Cualquier intento de JavaScript para acceder a ciertas APIs (por ejemplo, reproducción de audio, alertas) en un contexto en segundo plano finalizará de inmediato la renderización previa.
- Límite de instancias: Existe un límite para la cantidad de URLs con renderización previa activas permitidas por WebView.
Coincidencia de URL y No-Vary-Search (NVS)
WebView requiere un algoritmo de coincidencia confiable para garantizar que un recurso precargado solo se publique para su navegación prevista.
Comparación entre la concordancia exacta y la concordancia de NVS
De forma predeterminada, la recuperación previa y la renderización previa requieren una coincidencia exacta de la URL. Si la URL a la que se navegó es idéntica a la URL precargada, se entrega desde la caché de inmediato. Si los parámetros de consulta difieren, WebView usa las siguientes reglas de No-Vary-Search (NVS):
- La sugerencia: Los desarrolladores proporcionan una sugerencia
setExpectedNoVarySearchHeader()durante la inicialización. Si la URL a la que se navegó coincide con la URL de la solicitud, menos los parámetros sugeridos, WebView se bloquea brevemente para esperar los encabezados reales del servidor. - Encabezado del servidor: El encabezado de respuesta de NVS del servidor es la autoridad definitiva. Si el servidor confirma que se deben ignorar las diferencias en la búsqueda, la coincidencia se publica desde la caché. De lo contrario, WebView recurre a una carga de red en frío.
No-Vary-Search (NVS) es para uso avanzado, y es posible que la mayoría de los desarrolladores no lo necesiten porque pasan la misma URL exacta tanto a la carga previa como a la navegación (WebViewCompat.navigate o loadUrl). Esta guía solo es necesaria si hay diferencias en los parámetros de consulta entre la URL de carga previa y la URL navegada.
Configuración global
Ajusta el comportamiento de carga especulativa a nivel del perfil configurando los límites de PrefetchCache y la cantidad máxima de renderizaciones previas. También puedes restablecer los límites de la recuperación previa personalizada a los valores predeterminados del sistema:
Kotlin
// Configure prefetch cache limits
profile.prefetchCache.setMaxPrefetches(10)
profile.prefetchCache.setPrefetchTtlSeconds(60)
// Reset to system defaults when needed
profile.prefetchCache.clearMaxPrefetches()
// Configure maximum active prerenders
profile.setMaxPrerenders(2)
Java
// Configure prefetch cache limits
PrefetchCache prefetchCache = profile.getPrefetchCache();
prefetchCache.setMaxPrefetches(10);
prefetchCache.setPrefetchTtlSeconds(60);
// Reset to system defaults when needed
prefetchCache.clearMaxPrefetches();
// Configure maximum active prerenders
profile.setMaxPrerenders(2);
Manejo de errores y excepciones
Las operaciones especulativas usan un OutcomeReceiverCompat o un PrerenderOperationCallback para informar los resultados.
Excepciones principales
Cuando falla una operación de carga especulativa, el controlador de errores informa uno de los siguientes tipos de excepción principales para ayudarte a diagnosticar situaciones de falla específicas:
PrefetchException: Es la clase base para todos los errores de recuperación previa asíncrona.PrefetchNetworkException: Indica una falla a nivel de la red o del servidor. Puede incluir un campohttpStatusCode(por ejemplo, 404 o 503) para ayudar a diagnosticar problemas del servidor.PrerenderException: Es la superclase para todos los errores relacionados con la renderización previa, como las fallas debido a la presión de la memoria o el uso de APIs no permitidas (como la reproducción de audio) en segundo plano.
Estrategias de optimización
Sigue estas recomendaciones para maximizar los beneficios de la carga especulativa y conservar los recursos del sistema:
- Inicia la recuperación anticipada pronto: Comienza la recuperación anticipada durante el inicio de la app o tan pronto como sea probable un destino de navegación.
- Combina las sugerencias de QUIC con el precalentamiento de la conexión: Llama a
addQuicHints()antes de iniciarpreconnect(),prefetchUrlAsync()o navegaciones estándar para garantizar que WebView intente establecer conexiones con HTTP/3. - Estrategia integrada: Si renderizas previamente una URL que ya está en la caché de la recuperación previa, la navegación de la renderización previa se entrega desde esa caché, lo que evita solicitudes de red redundantes.
- Supervisa las cuotas: La renderización previa consume muchos recursos. Prefiere la recuperación previa para varios candidatos probables y reserva la renderización previa para la navegación más probable.
- Compatibilidad con esquemas: Asegúrate de que todas las URLs usen el esquema HTTPS obligatorio. Los esquemas no válidos o las entradas nulas activan un
IllegalArgumentExceptionsíncrono.
Recursos adicionales
Para obtener más información sobre la depuración de apps web, la optimización del rendimiento de inicio de WebView y el control de la finalización del proceso del renderizador, consulta los siguientes recursos:
- Navegación de páginas mejorada con el método
WebViewCompat.navigate - Cómo depurar aplicaciones web
- Optimiza el inicio de WebView
- Cómo controlar la finalización del proceso del renderizador de WebView