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 d'afficher du contenu avant que l'utilisateur n'y accède explicitement.

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

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.
  • 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 : 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 des URL, ce qui signifie qu'elles nécessitent le chemin d'accès exact à la page Web.

Comme Preconnect fonctionne au niveau de l'origine, il peut être initié 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 Prerender
Objectif principal Échauffer la connexion Mise en cache du 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é à un 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 probable 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 pour une origine spécifiée.

Contrairement à "Préchargement" et "Prérendu", qui nécessitent une URL de destination exacte, "Préconnexion" 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 cross-origin 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
}

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 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é est autorisé à être utilisé, les déclencheurs shouldInterceptRequest() se déclenchent normalement pour toutes les sous-ressources ultérieures (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 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 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 en 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 à la fois au préchargement et à loadUrl(). Ces consignes ne sont nécessaires que si les paramètres de requête diffèrent entre l'URL de préchargement et l'URL loadUrl().

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 profiter pleinement des avantages du chargement spéculatif tout en préservant les ressources système :

  • Initiez 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.
  • 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 :