Chargement spéculatif dans WebView

La latence de navigation est une métrique essentielle pour l'expérience utilisateur. Pour aider les développeurs à réduire cette latence, WebView fournit des API pour le chargement spéculatif et l'optimisation de la connexion, ce qui permet à votre application de récupérer ou d'afficher du contenu avant que l'utilisateur n'y accède explicitement.

WebView est compatible avec trois principaux types de chargement spéculatif : la préconnexion, la prélecture et le prérendu, ainsi qu'avec les indications QUIC pour optimiser la négociation de protocole.

En implémentant une stratégie de chargement spéculatif, vous pouvez obtenir les avantages suivants :

  • Réduction significative de la latence de chargement du contenu Web : déplacez l'heure de début du réseau plus tôt dans le cycle de vie de l'application et privilégiez les protocoles plus rapides comme HTTP/3.
  • Taux de réussite de la navigation plus élevés : en préchauffant le réseau et le cache, les navigations sont moins susceptibles d'échouer en raison de problèmes réseau temporaires.
  • Meilleure réactivité perçue : le prérendu, en particulier, permet des transitions instantanées qui donnent l'impression que l'application est beaucoup plus rapide.

Choisir une stratégie de chargement spéculatif

La principale différence entre ces stratégies réside dans leur portée : les API d'indices Preconnect et QUIC sont basées sur l'origine, ce qui signifie qu'elles ne nécessitent que le domaine cible. Les API Prefetch et Prerender sont basées sur des URL, ce qui signifie qu'elles nécessitent le chemin d'accès exact à la page Web.

Étant donné que les indications de préconnexion et QUIC fonctionnent au niveau de l'origine, vous pouvez les lancer beaucoup plus tôt dans le cycle de vie de l'application, avant même de connaître le contenu ou la page spécifiques vers lesquels l'utilisateur va naviguer.

Le tableau suivant compare ces trois stratégies pour vous aider à choisir celle qui convient le mieux à votre cas d'utilisation :

Fonctionnalité Préconnexion Préchargement Prerender
Objectif principal Échauffer la connexion Mettre en cache le code HTML uniquement (sans JavaScript ni CSS) Prérendu de la page entière
Scope (Portée) Niveau du profil (partagé entre les WebViews) Niveau du profil (partagé entre les WebViews) Au niveau WebView (lié à une WebView spécifique)
API Jetpack WebKit androidx.webkit.Profile androidx.webkit.Profile androidx.webkit.WebViewCompat
Méthodes de l'API Core preconnect(...) prefetchUrlAsync(...) prerenderUrlAsync(...)
Configuration N/A PrefetchCache.setMaxPrefetches()
PrefetchCache.setPrefetchTtlSeconds()
setMaxPrerenders()
Utilisation des ressources Faible (réseau) Moyen (réseau, mémoire) Élevée (processeur, mémoire, réseau)
Quand l'utiliser Lorsque l'origine cible est connue, mais que l'URL spécifique n'a pas encore été déterminée. Lorsque l'URL exacte est connue et que la navigation est probable, avec la mise en cache partagée entre les WebViews. Lorsque l'URL exacte est connue et que la navigation est très certaine dans une WebView spécifique.
Avantages Configuration de connexion plus rapide pour n'importe quelle URL sur l'origine Chargement plus rapide du réseau pour les URL correspondantes Navigation instantanée dès l'activation

Préconnexion aux origines

La préconnexion accélère les futurs chargements en effectuant de manière préventive des recherches DNS et des handshakes TCP/TLS ou QUIC pour une origine spécifiée.

Contrairement à Prefetch et Prerender, qui nécessitent une URL de destination exacte, Preconnect est strictement basé sur l'origine. Cela vous permet d'effectuer l'appel Preconnect beaucoup plus tôt que Prefetch et Prerender.

Cette stratégie de faible ressource au niveau du profil réduit la latence initiale pour tout partage WebView de ce profil, à condition que l'origine n'ait pas déjà été visitée. La connexion reste ouverte pendant environ 30 secondes, ce qui profite à toutes les requêtes HTTP, navigations ou sous-ressources interorigines ultérieures en éliminant la surcharge du handshake.

Implémentation

Pour lancer une préconnexion, appelez preconnect(String url) sur une instance Profile. Cette API doit être appelée sur le thread UI et nécessite la prise en charge de WebViewFeature.PRECONNECT.

L'API fonctionne sur l'origine, mais pour plus de commodité, une URL complète peut être fournie (par exemple, https://www.example.com/index.html). Elle est automatiquement traitée comme un appel à l'origine (par exemple, https://www.example.com). Plusieurs origines peuvent être connectées en appelant cette API plusieurs fois.

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
}

Indiquer la compatibilité avec le protocole QUIC à l'aide d'indices QUIC

HTTP/3 (qui s'exécute sur le protocole de transport QUIC) offre des améliorations significatives de la latence par rapport à HTTP/2, y compris les handshakes 0-RTT, une meilleure résilience de la connexion et l'élimination du blocage en tête de ligne en cas de perte de paquets.

Par défaut, WebView ne tente une connexion QUIC que s'il a une indication que l'origine prend en charge QUIC, par exemple à partir d'un en-tête Alt-Svc ou d'un enregistrement DNS HTTPS d'une interaction précédente. Sans cette connaissance préalable, WebView utilise HTTP/2 ou HTTP/1.1 pour la connexion initiale.

L'appel de addQuicHints préremplit ces informations sur la compatibilité du protocole, ce qui permet à WebView de se connecter à l'aide de QUIC immédiatement lors de la toute première connexion aux origines spécifiées.

Préconnexion avec des indications QUIC

Bien que les indications Preconnect et QUIC soient des optimisations au niveau de l'origine sur un Profile, elles ont des rôles distincts et complémentaires :

  • preconnect : ouvre et maintient activement une connexion réseau (recherche DNS et handshake TCP/TLS ou QUIC) pendant environ 30 secondes. Étant donné qu'il maintient des connexions réseau actives ouvertes, il consomme des ressources de l'appareil et du réseau. Il doit donc être réservé aux origines cibles à forte probabilité.
  • addQuicHints : n'entraîne aucun trafic réseau immédiat. Il met à jour les propriétés du serveur en mémoire de la pile réseau Profile pour enregistrer la compatibilité avec le protocole. Comme sa surcharge est négligeable, vous pouvez configurer des indications QUIC lors du démarrage de l'application pour toutes les origines compatibles avec HTTP/3 connues.

Pour des performances optimales, appelez addQuicHints avant d'appeler preconnect, prefetchUrlAsync ou loadUrl. Cela garantit que toutes les préconnexions ou requêtes de page ultérieures négocient HTTP/3 dès le début.

Implémentation

Pour configurer les indications QUIC, appelez addQuicHints(Set<String> urls) sur une instance Profile. Vous devez appeler cette API sur le thread UI et vérifier que WebView est compatible avec la fonctionnalité WebViewFeature.ADD_QUIC_HINTS_V1.

Comme preconnect, addQuicHints fonctionne sur les origines, mais des URL complètes peuvent être fournies (comme https://www.example.com/index.html) et sont automatiquement normalisées à leur origine (https://www.example.com).

Cette méthode est additive : l'appeler plusieurs fois fusionne les origines fournies dans 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
}

Configuration courante : PrefetchParameters et PrerenderParameters

La prélecture et le prérendu utilisent PrefetchParameters ou PrerenderParameters pour personnaliser la requête. Ces classes vous permettent de fournir des en-têtes et des indications supplémentaires pour la correspondance des URL, comme les configurations 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();

Préfractionnement du contenu

La prélecture télécharge la ressource HTML principale d'une URL et la stocke dans le cache réseau du profil. Dans WebView, un Profile sert de conteneur pour les données du navigateur, y compris les cookies, le cache HTTP et les service workers. Comme la prélecture est une opération au niveau du profil, tout WebView associé à ce profil peut exploiter la réponse mise en cache.

Implémentation

Pour lancer un préchargement, appelez prefetchUrlAsync() sur une instance Profile. Cette opération n'est compatible qu'avec le schéma 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
            }
        }
    }
);

Cycle de vie de l'interception

La requête de préchargement WebView modifie le moment et la manière dont le rappel shouldInterceptRequest() est déclenché. Comme cela a un impact direct sur l'utilisation de votre contenu préchargé, il est essentiel de comprendre le cycle de vie en deux étapes :

Schéma illustrant le cycle de vie de l'interception du préchargement WebView en deux étapes lors des phases spéculative et de navigation.
Figure 1. Cycle de vie de l'interception en deux étapes pour les requêtes et les navigations de préchargement WebView.

1. Phase spéculative (requête de préchargement)

Lorsque prefetchUrlAsync() est appelé, WebView télécharge la ressource HTML principale en arrière-plan. shouldInterceptRequest() est complètement ignoré pour cette requête en arrière-plan. Toute logique personnalisée, tout jeton d'autorisation ou toute injection d'en-tête généralement gérés dans votre intercepteur ne sont pas appliqués à la ressource HTML préchargée.

2. Phase de navigation (activation de l'utilisateur)

Lorsque l'application accède explicitement à l'URL (par exemple, à l'aide de WebViewCompat.navigate ou loadUrl) ou que l'utilisateur clique sur un lien correspondant, WebView détermine s'il peut utiliser le cache préchargé :

  • Évaluation HTML principale : WebView déclenchera shouldInterceptRequest() pour le code HTML principal à ce moment-là. Pour diffuser correctement la page à partir du cache de préchargement, votre intercepteur doit renvoyer null. Si vous renvoyez un WebResourceResponse personnalisé, WebView respecte votre intercepteur et contourne entièrement le cache de préchargement.

  • Évaluation des sous-ressources : une fois que le code HTML préchargé peut être utilisé, les déclencheurs shouldInterceptRequest() se déclenchent normalement pour toutes les sous-ressources ultérieures (comme les images, les scripts et le code CSS) nécessaires pour terminer le rendu de la page.

Comportements clés

Les caractéristiques opérationnelles et les vérifications d'éligibilité suivantes régissent la façon dont WebView lance et gère les requêtes de préchargement :

  • Sécurité des threads : les requêtes peuvent être initiées à partir de n'importe quel thread.
  • Éligibilité : avant de lancer une récupération, WebView s'assure que la requête est sécurisée et contextuellement appropriée en vérifiant les points suivants :
    • Cookies existants : pour protéger la confidentialité des utilisateurs et éviter les effets secondaires de type CSRF, WebView peut ignorer la prélecture si la requête nécessite des cookies d'authentification spécifiques qui pourraient déclencher un changement d'état sur le serveur.
    • Présence du service worker : si un service worker contrôle déjà le champ d'application de l'URL, WebView peut s'en remettre au gestionnaire de récupération du service worker au lieu de lancer un préchargement réseau standard.
    • Disponibilité du proxy : WebView vérifie que le chemin réseau actuel (y compris les éventuels proxys configurés) est stable pour éviter l'échec des requêtes spéculatives dans des configurations réseau complexes.
  • Si une prélecture ne démarre pas (même avec des paramètres valides), c'est souvent parce que WebView a déterminé qu'une requête en arrière-plan pourrait interférer avec la session ou l'état de sécurité actuels de l'utilisateur.
  • Annulation : utilisez CancellationSignal pour mettre fin à une requête en cours et l'empêcher d'être mise en cache.

Prérendu des pages

Le prérendu crée des "contenus Web" masqués pour afficher une page en arrière-plan, y compris l'exécution de scripts et la récupération de sous-ressources. Le prérendu repose sur la même infrastructure sous-jacente que la prélecture. Si une application lance un prérendu, WebView effectue d'abord une prélecture de la réponse pour diffuser la navigation de prérendu, en évitant toute activité réseau redondante.

Implémentation

Le prérendu est une opération au niveau de l'instance WebView. Appelez prerenderUrlAsync() en utilisant WebViewCompat à partir du thread UI.

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).
    }
});

La prélecture et le prérendu sont entièrement asynchrones. prefetchUrlAsync() peut être appelé depuis n'importe quel thread, tandis que prerenderUrlAsync() doit être lancé depuis le thread d'UI.

Contraintes techniques

Pour équilibrer la navigation instantanée et l'état du système, WebView applique les contraintes d'exécution suivantes :

  • Pression mémoire : WebView annule les URL prérendues si l'appareil manque de RAM.
  • API non autorisées : toute tentative d'accès à certaines API (par exemple, la lecture audio ou les alertes) par JavaScript dans un contexte d'arrière-plan mettra immédiatement fin au prérendu.
  • Limite d'instances : le nombre d'URL prérendues actives autorisées par WebView est limité.

Mise en correspondance des URL et No-Vary-Search (NVS)

WebView nécessite un algorithme de correspondance fiable pour s'assurer qu'une ressource préchargée n'est diffusée que pour la navigation prévue.

Correspondance exacte ou correspondance NVS

Par défaut, la prélecture et le prérendu nécessitent une correspondance exacte de l'URL. Si l'URL vers laquelle l'utilisateur a accédé est identique à l'URL préchargée, elle est immédiatement diffusée à partir du cache. Si les paramètres de requête diffèrent, WebView utilise les règles No-Vary-Search (NVS) suivantes :

  • Indication : les développeurs fournissent une indication setExpectedNoVarySearchHeader() lors de l'initialisation. Si l'URL de navigation correspond à l'URL de la requête moins les paramètres suggérés, WebView se bloque brièvement pour attendre les en-têtes réels du serveur.
  • En-tête du serveur : l'en-tête de réponse NVS du serveur fait autorité. Si le serveur confirme que les différences de requête doivent être ignorées, la correspondance est diffusée à partir du cache. Sinon, WebView revient à un chargement réseau à froid.

No-Vary-Search (NVS) est destiné à un usage avancé. La plupart des développeurs n'en auront probablement pas besoin, car ils transmettent exactement la même URL pour le préchargement et la navigation (WebViewCompat.navigate ou loadUrl). Ces conseils ne sont nécessaires que s'il existe des différences dans les paramètres de requête entre l'URL de préchargement et l'URL de navigation.

Configuration globale

Ajustez le comportement de chargement spéculatif au niveau du profil en configurant les limites PrefetchCache et le nombre maximal de prérendus. Vous pouvez également rétablir les limites de préchargement personnalisées sur les valeurs par défaut du système :

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);

Gestion des erreurs et des exceptions

Les opérations spéculatives utilisent un OutcomeReceiverCompat ou un PrerenderOperationCallback pour signaler les résultats.

Exceptions principales

Lorsqu'une opération de chargement spéculatif échoue, votre gestionnaire d'erreurs signale l'un des types d'exception principaux suivants pour vous aider à diagnostiquer des scénarios d'échec spécifiques :

  • PrefetchException : classe de base pour toutes les erreurs de préchargement asynchrone.
  • PrefetchNetworkException : indique un échec au niveau du réseau ou du serveur. Il peut inclure un champ httpStatusCode (par exemple, 404 ou 503) pour aider à diagnostiquer les problèmes côté serveur.
  • PrerenderException : super-classe pour toutes les erreurs liées au prérendu, telles que les échecs dus à une pression de mémoire ou à l'utilisation d'API non autorisées (comme la lecture audio) en arrière-plan.

Stratégies d'optimisation

Suivez ces recommandations pour maximiser les avantages du chargement spéculatif tout en préservant les ressources système :

  • Lancez le préchargement tôt : commencez le préchargement au démarrage de l'application ou dès qu'une destination de navigation est probable.
  • Associez les indications QUIC au préchauffage de la connexion : appelez addQuicHints() avant de lancer preconnect(), prefetchUrlAsync() ou des navigations standards pour vous assurer que WebView tente d'établir des connexions à l'aide de HTTP/3.
  • Stratégie intégrée : si vous prérendez une URL déjà présente dans le cache de préchargement, la navigation de prérendu est diffusée à partir de ce cache, ce qui évite les requêtes réseau redondantes.
  • Surveillez les quotas : le prérendu consomme beaucoup de ressources. Privilégiez le préchargement pour plusieurs candidats probables et réservez le prérendu pour la navigation la plus probable.
  • Compatibilité des schémas : assurez-vous que toutes les URL utilisent le schéma HTTPS obligatoire. Les schémas non valides ou les entrées nulles déclenchent un IllegalArgumentException synchrone.

Ressources supplémentaires

Pour en savoir plus sur le débogage des applications Web, l'optimisation des performances de démarrage de WebView et la gestion de l'arrêt du processus de rendu, consultez les ressources suivantes :