Navigation améliorée sur les pages avec WebViewCompat.navigate

WebViewCompat.navigate est une alternative améliorée à WebView.loadUrl qui offre un contrôle précis sur le chargement des pages Web, la gestion de l'historique et le suivi du cycle de vie de la navigation dans WebView.

Auparavant, le lancement de la navigation sur une page à l'aide de loadUrl présentait des limites notables :

  • Aucun remplacement d'entrée d'historique : vous ne pouviez pas remplacer l'entrée d'historique actuelle, ce qui rendait impossible la navigation vers une nouvelle page sans ajouter d'entrée à la pile "Retour".
  • Rappels découplés : il n'existait aucun mécanisme direct pour corréler un appel loadUrl spécifique avec les événements de rappel ultérieurs dans WebViewClient.
  • En-têtes supplémentaires non enregistrés : les en-têtes personnalisés transmis à loadUrl n'étaient pas enregistrés dans l'état WebView. Ils étaient donc perdus lors de la restauration de l'état.

L'API WebViewCompat.navigate résout ces problèmes en introduisant les fonctionnalités suivantes :

  • Remplacement d'entrée d'historique de navigation : vous permet de remplacer la page actuelle dans la pile d'historique WebView.
  • Suivi des rappels corrélés : renvoie un objet Navigation qui sert d'identifiant unique à toutes les étapes d'un cycle de vie de navigation.
  • Prise en charge des en-têtes d'état enregistrés : les en-têtes supplémentaires sont enregistrés de manière fiable dans le bundle d'état WebView, ce qui leur permet d'être réutilisés lors de la restauration de l'état.

Fonctionnalités et limites clés

Avant d'adopter WebViewCompat.navigate, tenez compte des règles et contraintes opérationnelles suivantes :

  • Sécurité des threads : vous devez appeler WebViewCompat.navigate sur le thread d'interface utilisateur (principal).

  • Annulation et priorité : les navigations en cours ne peuvent pas être annulées explicitement. Toutefois, le lancement d'un nouvel appel navigate sur le même WebView remplace toute navigation active.

  • Prise en charge des schémas d'URI : les schémas d'URI standards (tels que https: et http:) et personnalisés sont compatibles. Le schéma javascript: n'est pas compatible.

  • Limite de taille des URL : la longueur maximale de la chaîne d'URL acceptée est de 2 Mo.

  • Vérification des fonctionnalités : vérifiez toujours la disponibilité des fonctionnalités à l'aide de WebViewFeature.isFeatureSupported avant d'appeler l'API pour maintenir la compatibilité entre les différentes versions d'APK WebView.

Lancer la navigation et suivre le cycle de vie

Pour configurer la navigation et suivre son cycle de vie, procédez comme suit :

  1. Enregistrez une NavigationListener implémentation à l'aide de WebViewCompat.addNavigationListener lors de la configuration WebView pour recevoir des rappels de cycle de vie structurés. Enregistrez l'écouteur une seule fois (plutôt qu'à chaque appel de navigation) pour éviter les fuites de mémoire et les exécutions de rappels en double.
  2. Créez une instance NavigationParameters à l'aide de NavigationParameters.Builder pour spécifier des comportements facultatifs, tels que le remplacement de l'historique ou des en-têtes HTTP personnalisés.
  3. Appelez WebViewCompat.navigate, en transmettant votre instance WebView, l' URL de destination et les paramètres.

WebViewCompat.navigate renvoie un Navigation objet qui identifie de manière unique la requête. Dans vos NavigationListener rappels, comparez cet objet avec le paramètre Navigation entrant pour suivre cette navigation spécifique.

Exemple de mise en œuvre

L'exemple suivant montre comment configurer les paramètres de navigation, appeler WebViewCompat.navigate et écouter les événements du cycle de vie de la navigation :

Kotlin

class WebNavigationManager(private val webView: WebView) {
    // Track the navigation instance returned by the API
    private var currentNavigation: Navigation? = null

    init {
        // 1. Define listener to observe navigation lifecycle events
        val listener = object : NavigationListener {
            override fun onNavigationStarted(navigation: Navigation) {
                if (navigation == currentNavigation) {
                    // Navigation started
                }
            }

            override fun onNavigationRedirected(navigation: Navigation) {
                if (navigation == currentNavigation) {
                    // Navigation encountered a redirect
                }
            }

            override fun onNavigationCompleted(navigation: Navigation) {
                if (navigation == currentNavigation) {
                    if (navigation.didCommit()) {
                        // Navigation committed successfully
                    } else if (navigation.didCommitErrorPage()) {
                        // Navigation committed an error page
                        val statusCode = navigation.statusCode
                        val error = navigation.webResourceError
                    }
                }
            }

            override fun onFirstContentfulPaintMillis(page: Page, durationMillis: Long) {
                // Match page with current navigation
                if (page == currentNavigation?.page) {
                    // Page rendering started (First Contentful Paint achieved)
                }
            }
        }

        // 2. Register listener on the main thread
        WebViewCompat.addNavigationListener(webView, listener)
    }

    @UiThread
    fun navigateToPage(url: String) {
        // Check feature availability
        if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
            // Fall back to standard loadUrl if navigate API is unavailable
            webView.loadUrl(url)
            return
        }

        // 3. Configure navigation parameters
        val params = NavigationParameters.Builder()
            .setShouldReplaceCurrentEntry(true)
            .addAdditionalHeaders(
                mapOf("X-Test-Navigate-Header" to "TestValue")
            )
            .build()

        // 4. Initiate navigation on the UI thread
        currentNavigation = WebViewCompat.navigate(webView, url, params)
    }
}

Java

public class WebNavigationManager {

    private Navigation mCurrentNavigation;
    private final WebView mWebView;

    public WebNavigationManager(@NonNull WebView webView) {
        mWebView = webView;
        setupListener();
    }

    private void setupListener() {
        // 1. Define listener to observe navigation lifecycle events
        NavigationListener listener = new NavigationListener() {
            @Override
            public void onNavigationStarted(@NonNull Navigation navigation) {
                if (navigation.equals(mCurrentNavigation)) {
                    // Navigation started
                }
            }

            @Override
            public void onNavigationRedirected(@NonNull Navigation navigation) {
                if (navigation.equals(mCurrentNavigation)) {
                    // Navigation encountered a redirect
                }
            }

            @Override
            public void onNavigationCompleted(@NonNull Navigation navigation) {
                if (navigation.equals(mCurrentNavigation)) {
                    if (navigation.didCommit()) {
                        // Navigation committed successfully
                    } else if (navigation.didCommitErrorPage()) {
                        // Navigation committed an error page
                        int statusCode = navigation.getStatusCode();
                        WebResourceErrorCompat error = navigation.getWebResourceError();
                    }
                }
            }

            @Override
            public void onFirstContentfulPaintMillis(@NonNull Page page, long durationMillis) {
                if (mCurrentNavigation != null && page.equals(mCurrentNavigation.getPage())) {
                    // Page rendering started (First Contentful Paint achieved)
                }
            }
        };

        // 2. Register listener on the main thread
        WebViewCompat.addNavigationListener(mWebView, listener);
    }

    @UiThread
    public void navigateToPage(@NonNull String url) {
        // Check feature availability
        if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
            // Fall back to standard loadUrl if navigate API is unavailable
            mWebView.loadUrl(url);
            return;
        }

        // 3. Configure navigation parameters
        NavigationParameters params = new NavigationParameters.Builder()
            .setShouldReplaceCurrentEntry(true)
            .addAdditionalHeaders(Collections.singletonMap(
                "X-Test-Navigate-Header", "TestValue"
            ))
            .build();

        // 4. Initiate navigation on the UI thread
        mCurrentNavigation = WebViewCompat.navigate(mWebView, url, params);
    }
}

Modes de défaillance et gestion des erreurs

L'API WebViewCompat.navigate fournit des mécanismes distincts pour gérer les erreurs de configuration et les échecs de navigation lors de l'exécution :

Exceptions d'arguments non valides

La transmission d'arguments non valides déclenche une IllegalArgumentException synchrone. Les causes les plus courantes sont les suivantes :

  • Transmission de null pour les paramètres obligatoires non nuls (webView, url ou params).
  • Fourniture d'un schéma d'URL non compatible, tel que javascript:.
  • Transmission de clés ou de valeurs d'en-tête HTTP mal formées qui ne sont pas conformes aux spécifications RFC 2616.

En cas d'échec lors de la requête réseau ou du chargement de page (par exemple, un code d'état HTTP 404 état, un échec de résolution DNS ou une erreur SSL), WebViewCompat.navigate renvoie toujours un objet Navigation valide.

Une fois la navigation terminée, examinez les méthodes suivantes sur l'Navigation instance dans votre onNavigationCompleted rappel pour diagnostiquer l' échec :

  • getStatusCode: renvoie le code d'état de la réponse HTTP (par exemple, 404 ou 500).
  • getWebResourceError : renvoie un objet WebResourceErrorCompat détaillant les erreurs réseau, telles que les délais d'inactivité de connexion ou les échecs de recherche d'hôte.
  • didCommitErrorPage: indique si WebView a validé et affiché une page d'erreur à l'utilisateur.
  • didCommit: indique si la navigation a été validée avec succès dans une page cible sans être interrompue.

Gestion des bundles d'état enregistrés

Lorsque vous transmettez des en-têtes supplémentaires avec NavigationParameters, WebView les enregistre dans son bundle d'état enregistré afin qu'ils puissent être réutilisés lors de la restauration de l'état. Toutefois, les grandes collections d'en-têtes peuvent augmenter considérablement la taille du Bundle d'état enregistré.

Si vous devez limiter la taille du bundle pour éviter TransactionTooLargeException lors des enregistrements d'état Android, utilisez WebViewCompat.saveState. Cette méthode vous permet de définir une limite de taille maximale du bundle en octets et d'exclure éventuellement les éléments d'historique avant :

Kotlin

// Save state with a maximum bundle size limit (for example, 64 KB)
val maxSizeBytes = 64 * 1024
val includeForwardState = false
val outState = Bundle()

WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState)

Java

// Save state with a maximum bundle size limit (for example, 64 KB)
int maxSizeBytes = 64 * 1024;
boolean includeForwardState = false;
Bundle outState = new Bundle();

WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState);

Le bundle résultant reste compatible avec la méthode standard WebView.restoreState.

Recommandations de migration et de mise en œuvre

Pour garantir des performances et une stabilité optimales lors de la navigation dans WebView, suivez ces recommandations :

  • Migrer de loadUrl vers navigate: migrez tous les appels hérités WebView.loadUrl vers WebViewCompat.navigate. Cela garantit une gestion uniforme de l'historique et que les en-têtes sont toujours enregistrés dans l'état enregistré.

  • Vérifier toujours la prise en charge des fonctionnalités : avant d'appeler l'API, vérifiez la prise en charge de l'exécution avec WebViewFeature.isFeatureSupported pour vous protéger contre les anciennes versions de WebView.

  • Corréler les instances de navigation : utilisez l'objet Navigation renvoyé pour différencier les navigations simultanées ou filtrer les rappels lorsque vous gérez plusieurs instances WebView.

  • Enregistrer l'écouteur une seule fois lors de l'initialisation : étant donné que WebViewCompat.addNavigationListener ajoute un écouteur au lieu d'en remplacer un existant, enregistrez votre NavigationListener une seule fois lors de la configuration de WebView pour éviter les fuites de mémoire et les exécutions de rappels en double lors des navigations ultérieures.

  • Surveiller la taille de l'état enregistré : lorsque vous transmettez des charges utiles d'en-tête volumineuses, utilisez WebViewCompat.saveState avec des limites de taille explicites pour éviter d'enregistrer des données d'état excessives.

Ressources supplémentaires

Pour en savoir plus sur les fonctionnalités Web intégrées et l'optimisation des performances, consultez les guides suivants :