WebViewCompat.navigate est une alternative améliorée à WebView.loadUrl qui permet de contrôler précisément le chargement des pages Web, la gestion de l'historique et le suivi du cycle de vie de la navigation dans WebView.
Auparavant, l'initiation de la navigation sur les pages à l'aide de loadUrl présentait des limites importantes :
- Aucun remplacement de l'entrée d'historique : vous n'avez pas pu remplacer l'entrée d'historique actuelle, ce qui vous a empêché d'accéder à une nouvelle page sans ajouter d'entrée à la pile "Retour".
- Rappels découplés : il n'existait aucun mécanisme direct permettant de corréler un appel
loadUrlspécifique avec les événements de rappel ultérieurs dansWebViewClient. - En-têtes supplémentaires non enregistrés : les en-têtes personnalisés transmis à
loadUrln'ont pas été enregistrés dans l'étatWebView. Ils ont donc été perdus lors de la restauration de l'état.
L'API WebViewCompat.navigate résout ces problèmes en proposant les fonctionnalités suivantes :
- Remplacement d'une entrée de l'historique de navigation : vous permet de remplacer la page actuelle dans la pile de l'historique
WebView. - Suivi des rappels corrélés : renvoie un objet
Navigationqui sert d'identifiant unique à toutes les étapes d'un cycle de vie de navigation. - Prise en charge de l'en-tête d'état enregistré : les en-têtes supplémentaires sont enregistrés de manière fiable dans le bundle d'état
WebViewafin de pouvoir être réutilisés lors de la restauration de l'état.
Principales fonctionnalités et limites
Avant d'adopter WebViewCompat.navigate, tenez compte des règles et contraintes opérationnelles suivantes :
Sécurité des threads : vous devez appeler
WebViewCompat.navigatesur le thread UI (principal).Annulation et priorité : les navigations en cours ne peuvent pas être annulées explicitement. Toutefois, le lancement d'un nouvel appel
navigatesur le mêmeWebViewremplace toute navigation active.Compatibilité avec les schémas d'URI : les schémas d'URI standards (tels que
https:ethttp:) et personnalisés sont acceptés. Le schémajavascript:n'est pas accepté.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.isFeatureSupportedavant d'appeler l'API pour maintenir la compatibilité entre les différentes versions APK de WebView.
Lancer la navigation et suivre le cycle de vie
Pour configurer la navigation et suivre son cycle de vie, procédez comme suit :
- Enregistrez une implémentation
NavigationListenerà l'aide deWebViewCompat.addNavigationListenerlors de la configuration deWebViewpour 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 rappel en double. - Construisez une instance
NavigationParametersà l'aide deNavigationParameters.Builderpour spécifier des comportements facultatifs, tels que le remplacement de l'historique ou des en-têtes HTTP personnalisés. - Appelez
WebViewCompat.navigateen transmettant votre instanceWebView, l'URL de destination et les paramètres.
WebViewCompat.navigate renvoie un objet Navigation qui identifie de manière unique la requête. Dans vos rappels NavigationListener, 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 de 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);
}
}
Propager l'état de l'application à l'aide d'en-têtes HTTP
Les applications Web ont souvent besoin du contexte de l'application hôte Android pour coordonner la logique du backend ou personnaliser le contenu Web. L'ajout de paramètres de requête à l'URL pour transmettre ces informations peut encombrer les URL, interférer avec la mise en cache et exposer l'état interne de l'application.
Nous vous recommandons plutôt de transmettre le contexte de l'application à l'aide d'en-têtes HTTP personnalisés. En utilisant WebViewCompat.navigate et NavigationParameters, vous pouvez envoyer ces données de manière sécurisée à votre serveur. De plus, WebView conserve ces en-têtes lors de la restauration de l'état, ce qui garantit que le contenu Web reste cohérent lors des modifications de configuration. Notez que cette persistance ne s'applique que lorsque vous utilisez WebViewCompat.navigate. Si vous utilisez WebView.loadUrl, les en-têtes personnalisés ne sont pas enregistrés dans le bundle d'état WebView et sont perdus lors de la restauration.
Cas d'utilisation courants
Voici quelques cas d'utilisation courants pour transmettre le contexte de l'application hôte :
- Version de l'application (
X-App-Version) : le fait de transmettre la version de publication de l'application hôte (par exemple,BuildConfig.VERSION_NAME) aide votre serveur backend à vérifier la compatibilité du pont JavaScript natif, à limiter l'accès à certaines fonctionnalités ou à inviter les utilisateurs à mettre à jour les anciennes applications. - Plate-forme client (
X-Client-Platform) : l'identification explicite de l'environnement hôte en tant qu'Android permet au serveur de fournir une UI adaptée à la plate-forme ou des liens vers la plate-forme de téléchargement d'applications sans s'appuyer sur l'analyse de la chaîneUser-Agent.
Exemple de mise en œuvre
L'exemple suivant montre comment transmettre la version de l'application et la plate-forme client à un serveur Web :
Kotlin
// Attach host app metadata so the server can verify compatibility and tailor content
val params = NavigationParameters.Builder()
.addAdditionalHeaders(
mapOf(
"X-App-Version" to BuildConfig.VERSION_NAME,
"X-Client-Platform" to "Android"
)
)
.build()
// Use navigate instead of loadUrl to retain custom headers across state restoration
WebViewCompat.navigate(webView, "https://www.example.com", params)
Java
// Attach host app metadata so the server can verify compatibility and tailor content
Map<String, String> headers = new HashMap<>();
headers.put("X-App-Version", BuildConfig.VERSION_NAME);
headers.put("X-Client-Platform", "Android");
NavigationParameters params = new NavigationParameters.Builder()
.addAdditionalHeaders(headers)
.build();
// Use navigate instead of loadUrl to retain custom headers across state restoration
WebViewCompat.navigate(webView, "https://www.example.com", 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.
Voici quelques causes courantes :
- Transmettre
nullpour les paramètres obligatoires non nuls (webView,urlouparams). - Fournir un schéma d'URL non compatible, tel que
javascript:. - Transmettre des clés ou des valeurs d'en-tête HTTP mal formées qui ne sont pas conformes aux spécifications RFC 2616.
Erreurs de processus de navigation
En cas d'échec de la requête réseau ou du chargement de la page (par exemple, code d'état HTTP 404, échec de la résolution DNS ou erreur SSL), WebViewCompat.navigate renvoie toujours un objet Navigation valide.
Une fois la navigation terminée, inspectez les méthodes suivantes sur l'instance Navigation dans votre rappel onNavigationCompleted pour diagnostiquer l'échec :
getStatusCode: renvoie le code d'état de la réponse HTTP (par exemple,404ou500).getWebResourceError: renvoie un objetWebResourceErrorCompatdétaillant les erreurs réseau, telles que les délais d'inactivité de connexion ou les échecs de recherche d'hôte.didCommitErrorPage: indique siWebViewa validé et affiché une page d'erreur à l'utilisateur.didCommit: indique si la navigation a bien été effectuée sur une page cible sans être interrompue.
Gestion des groupes d'état enregistrés
Lorsque vous transmettez des en-têtes supplémentaires avec NavigationParameters, WebView enregistre ces en-têtes 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 de l'état enregistré Bundle.
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 pour le bundle (en octets) et d'exclure éventuellement les éléments de l'historique "Précédent" :
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 obtenu reste compatible avec la méthode standard WebView.restoreState.
Recommandations de migration et d'implémentation
Pour garantir des performances et une stabilité optimales lorsque vous naviguez dans WebView, suivez ces recommandations :
Migrer de
loadUrlversnavigate: migrez tous les anciens appelsWebView.loadUrlversWebViewCompat.navigate. Cela garantit une gestion uniforme de l'historique et assure que les en-têtes sont toujours enregistrés dans l'état enregistré.Vérifiez toujours la compatibilité des fonctionnalités : avant d'appeler l'API, vérifiez la compatibilité de l'exécution avec
WebViewFeature.isFeatureSupportedpour vous prémunir contre les anciennes versions de WebView.Corréler les instances de navigation : utilisez l'objet
Navigationrenvoyé pour différencier les navigations simultanées ou filtrer les rappels lorsque vous gérez plusieurs instancesWebView.Enregistrez l'écouteur une seule fois lors de l'initialisation : comme
WebViewCompat.addNavigationListenerajoute un écouteur au lieu de remplacer un écouteur existant, enregistrez votreNavigationListenerune seule fois lors de la configuration deWebViewpour éviter les fuites de mémoire et les exécutions de rappel en double lors des navigations ultérieures.Surveillez la taille de l'état de sauvegarde : lorsque vous transmettez de grandes charges utiles d'en-tête, utilisez
WebViewCompat.saveStateavec 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 :
- Simplifiez l'implémentation de WebView avec Jetpack Webkit
- Chargement spéculatif dans WebView
- Optimiser le démarrage de WebView
- Gérer l'arrêt du processus de rendu WebView