Navigazione nelle pagine migliorata con WebViewCompat.navigate

WebViewCompat.navigate è un'alternativa avanzata a WebView.loadUrl che fornisce un controllo granulare sul caricamento delle pagine web, sulla gestione della cronologia e sul monitoraggio del ciclo di vita della navigazione in WebView.

In precedenza, l'avvio delle navigazioni nelle pagine utilizzando loadUrl presentava notevoli limitazioni:

  • Nessuna sostituzione della voce della cronologia: non era possibile sostituire la voce della cronologia corrente, il che rendeva impossibile passare a una nuova pagina senza aggiungere una voce al back stack.
  • Callback disaccoppiati: non esisteva un meccanismo diretto per correlare una chiamata loadUrl specifica con gli eventi di callback successivi in WebViewClient.
  • Intestazioni aggiuntive non salvate: le intestazioni personalizzate passate a loadUrl non venivano salvate come parte dello stato WebView, quindi andavano perse durante il ripristino dello stato.

L'API WebViewCompat.navigate risolve questi problemi introducendo le seguenti funzionalità:

  • Sostituzione della voce della cronologia di navigazione: consente di sostituire la pagina corrente nello stack della cronologia WebView.
  • Monitoraggio dei callback correlati: restituisce un oggetto Navigation che funge da identificatore univoco in tutte le fasi del ciclo di vita della navigazione.
  • Supporto delle intestazioni dello stato salvato: le intestazioni aggiuntive vengono salvate in modo affidabile nel bundle di stato WebView in modo che possano essere riutilizzate al ripristino dello stato.

Funzionalità e limitazioni principali

Prima di adottare WebViewCompat.navigate, tieni presente le seguenti regole e limitazioni operative:

  • Thread safety: devi richiamare WebViewCompat.navigate nel thread dell'interfaccia utente (principale).

  • Annullamento e precedenza: le navigazioni in corso non possono essere annullate esplicitamente. Tuttavia, l'avvio di una nuova chiamata navigate sullo stesso WebView sostituisce qualsiasi navigazione attiva.

  • Supporto dello schema URI: sono supportati gli schemi URI standard (ad esempio https: e http:) e personalizzati. Lo schema javascript: non è supportato.

  • Limite di dimensioni dell'URL: la lunghezza massima della stringa URL supportata è di 2 MB.

  • Controllo delle funzionalità: controlla sempre la disponibilità delle funzionalità utilizzando WebViewFeature.isFeatureSupported prima di richiamare l'API per mantenere la compatibilità tra le diverse versioni APK di WebView.

Avviare la navigazione e monitorare il ciclo di vita

Per configurare la navigazione e monitorarne il ciclo di vita:

  1. Registra un NavigationListener implementazione utilizzando WebViewCompat.addNavigationListener durante WebView configurazione per ricevere callback strutturati del ciclo di vita. Registra il listener una sola volta (anziché a ogni chiamata di navigazione) per evitare perdite di memoria ed esecuzioni di callback duplicate.
  2. Crea un'istanza di NavigationParameters utilizzando NavigationParameters.Builder per specificare comportamenti facoltativi, come la sostituzione della cronologia o le intestazioni HTTP personalizzate.
  3. Chiama WebViewCompat.navigate, passando l'istanza WebView, l' URL di destinazione e i parametri.

WebViewCompat.navigate restituisce un oggetto Navigation che identifica in modo univoco la richiesta. Nei callback NavigationListener, confronta questo oggetto con il parametro Navigation in entrata per monitorare la navigazione specifica.

Esempio di implementazione

L'esempio seguente mostra come configurare i parametri di navigazione, richiamare WebViewCompat.navigate e ascoltare gli eventi del ciclo di vita della navigazione:

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

Modalità di errore e gestione degli errori

L'API WebViewCompat.navigate fornisce meccanismi distinti per la gestione degli errori di configurazione e degli errori di navigazione di runtime:

Eccezioni di argomenti non validi

Il passaggio di argomenti non validi attiva un'eccezione IllegalArgumentException sincrona. Di seguito sono elencate alcune delle possibili cause:

  • Passaggio di null per i parametri non null obbligatori (webView, url o params).
  • Fornitura di uno schema URL non supportato, ad esempio javascript:.
  • Passaggio di chiavi o valori di intestazione HTTP non validi che non sono conformi alle specifiche RFC 2616.

Se si verifica un errore durante la richiesta di rete o il caricamento pagina (ad esempio un codice di stato HTTP 404 codice di stato, un errore di risoluzione DNS o un errore SSL), WebViewCompat.navigate restituisce comunque un oggetto Navigation valido.

Al termine della navigazione, esamina i seguenti metodi nell'Navigation istanza all'interno del onNavigationCompleted callback per diagnosticare l' errore:

  • getStatusCode: restituisce il codice di stato della risposta HTTP (ad esempio 404 o 500).
  • getWebResourceError: restituisce un oggetto WebResourceErrorCompat che descrive in dettaglio gli errori di rete, come i timeout di connessione o gli errori di ricerca dell'host.
  • didCommitErrorPage: indica se WebView ha eseguito il commit e visualizzato una pagina di errore per l'utente.
  • didCommit: indica se la navigazione è stata eseguita correttamente in una pagina di destinazione senza essere interrotta.

Gestione del bundle di stato salvato

Quando passi intestazioni aggiuntive con NavigationParameters, WebView salva queste intestazioni nel bundle di stato salvato in modo che possano essere riutilizzate al ripristino dello stato. Tuttavia, le raccolte di intestazioni di grandi dimensioni possono aumentare notevolmente le dimensioni del Bundle di stato salvato.

Se devi limitare le dimensioni del bundle per evitare TransactionTooLargeException durante i salvataggi dello stato di Android, utilizza WebViewCompat.saveState. Questo metodo consente di impostare un limite massimo di dimensioni del bundle in byte e, facoltativamente, di escludere gli elementi della cronologia in avanti:

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

Il bundle risultante rimane compatibile con il metodo standard WebView.restoreState.

Suggerimenti per la migrazione e l'implementazione

Per garantire prestazioni e stabilità ottimali durante la navigazione in WebView, segui questi consigli:

  • Esegui la migrazione da loadUrl a navigate: esegui la migrazione di tutte le chiamate legacy WebView.loadUrl a WebViewCompat.navigate. In questo modo, la gestione della cronologia è uniforme e le intestazioni vengono sempre salvate come parte dello stato salvato.

  • Verifica sempre il supporto delle funzionalità: prima di richiamare l'API, verifica il supporto di runtime con WebViewFeature.isFeatureSupported per proteggerti dalle versioni precedenti di WebView.

  • Correlare le istanze di navigazione: utilizza l'oggetto Navigation restituito per distinguere le navigazioni simultanee o filtrare i callback quando gestisci più istanze WebView.

  • Registra il listener una sola volta durante l'inizializzazione: poiché WebViewCompat.addNavigationListener aggiunge un listener anziché sostituire uno esistente, registra NavigationListener una sola volta durante WebView la configurazione per evitare perdite di memoria ed esecuzioni di callback duplicate nelle navigazioni successive.

  • Monitora le dimensioni dello stato salvato: quando passi payload di intestazione di grandi dimensioni, utilizza WebViewCompat.saveState con limiti di dimensioni espliciti per evitare di salvare dati di stato eccessivi.

Risorse aggiuntive

Per saperne di più sulle funzionalità web incorporate e sull'ottimizzazione del rendimento, consulta le seguenti guide: