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, ce qui permet à votre application de récupérer ou de rendre du contenu avant que l'utilisateur n'y accède explicitement.

WebView accepte trois principaux types de chargement spéculatif : la préconnexion, le préchargement et le prérendu.

En implémentant une stratégie de chargement spéculatif, vous pouvez obtenir les résultats 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.
  • 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 rendent l'application 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 : l'API Preconnect est basée sur l'origine, ce qui signifie qu'elle ne nécessite que le domaine cible. Les API Prefetch et Prerender sont basées sur l'URL, ce qui signifie qu'elles nécessitent le chemin d'accès exact de la page Web.

Comme Preconnect fonctionne au niveau de l'origine, il peut être lancé beaucoup plus tôt dans le cycle de vie de l'application, avant même que vous ne connaissiez 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 Prérendu
Objectif principal Préchauffer la connexion Mettre en cache le code HTML uniquement (sans JavaScript ni CSS) Prérendre la page entière
Scope (Portée) Niveau du profil (partagé entre les WebView) Niveau du profil (partagé entre les WebView) Niveau WebView (lié à une WebView spécifique)
API Jetpack WebKit androidx.webkit.Profile androidx.webkit.Profile androidx.webkit.WebViewCompat
Méthodes d'API principales preconnect(...) prefetchUrlAsync(...) prerenderUrlAsync(...)
Configuration N/A PrefetchCache.setMaxPrefetches()
PrefetchCache.setPrefetchTtlSeconds()
setMaxPrerenders()
Utilisation des ressources Faible (réseau) Moyenne (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'est pas encore déterminée. Lorsque l'URL exacte est connue et que la navigation est probable, avec une mise en cache partagée entre les WebView. 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 de l'origine Chargement réseau plus rapide pour les URL correspondantes Navigation vraiment instantanée lors de l'activation

Se connecter à l'avance aux origines

La préconnexion accélère les chargements futurs en effectuant de manière préemptive des recherches DNS et des handshakes TCP/TLS 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 toute WebView partageant 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 inter-origines ultérieures en éliminant les frais de 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
}

Configuration courante : PrefetchParameters et PrerenderParameters

Prefetch et Prerender 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 mise en correspondance des URL, telles que 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écharger du contenu

Le préchargement 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 le préchargement 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 réussie ou non 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.
Image 1. Cycle de vie d'interception en deux étapes pour les requêtes de préchargement et les navigations 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, jeton d'autorisation ou 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 par 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éclenche 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é est autorisé à être utilisé, shouldInterceptRequest() se déclenche normalement pour toutes les sous-ressources suivantes (telles que les images, les scripts et le 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 manière dont WebView lance et gère les requêtes de préchargement :

  • Sécurité des threads : les requêtes peuvent être lancé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 appropriée sur le plan contextuel 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 le préchargement si la requête nécessite des cookies authentifiés spécifiques qui pourraient déclencher un changement d'état sur le serveur.
    • Présence d'un 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 plutôt que de lancer un préchargement réseau standard.
    • Disponibilité du proxy : WebView vérifie que le chemin réseau actuel (y compris les proxys configurés) est stable pour éviter l'échec des requêtes spéculatives dans des configurations réseau complexes.
  • Si un préchargement 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érendre des pages

Le prérendu crée des "contenus Web" masqués pour afficher entièrement 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 le préchargement. Si une application lance un prérendu, WebView effectue d'abord un préchargement 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() à l'aide de 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).
    }
});

Le préchargement et le prérendu sont entièrement asynchrones. prefetchUrlAsync() peut être appelé à partir de n'importe quel thread, tandis que prerenderUrlAsync() doit être lancé à partir du thread UI.

Contraintes techniques

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

  • Pression sur la mémoire : WebView annule les URL prérendues si l'appareil manque de RAM.
  • API non autorisées : toute tentative d'accès par JavaScript à certaines API (par exemple, la lecture audio, les alertes) dans un contexte d'arrière-plan met 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 mise en correspondance fiable pour s'assurer qu'une ressource préchargée n'est diffusée que pour la navigation prévue.

Correspondance exacte ou NVS

Par défaut, le préchargement et le prérendu nécessitent une correspondance exacte de l'URL. Si l'URL de navigation 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 indiqué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 est l'autorité ultime. 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é, et la plupart des développeurs n'en auront peut-être pas besoin , car ils transmettent exactement la même URL au préchargement et à la navigation (WebViewCompat.navigate ou loadUrl). Ces instructions 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 par défaut :

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 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 asynchrones.
  • PrefetchNetworkException : indique un échec au niveau du réseau ou du serveur. Il peut inclure un champ httpStatusCode (tel que 404 ou 503) pour vous aider à diagnostiquer les problèmes côté serveur.
  • PrerenderException : superclasse pour toutes les erreurs liées au prérendu, telles que les échecs dus à une pression sur la 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 :

  • Lancer tôt : commencez le préchargement au démarrage de l'application ou dès qu'une destination de navigation est probable.
  • 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.
  • Surveiller les quotas : le prérendu est gourmand en ressources. Préférez 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 une 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 :