Verbesserte Seitennavigation mit WebViewCompat.navigate

WebViewCompat.navigate ist eine erweiterte Alternative zu WebView.loadUrl, mit der Sie die Steuerung des Ladens von Webseiten, der Verlaufsverwaltung und des Tracking des Navigationslebenszyklus in WebView detailliert steuern können.

Bisher gab es bei der Initiierung der Seitennavigation mit loadUrl erhebliche Einschränkungen:

  • Kein Ersetzen von Verlaufseinträgen:Sie konnten den aktuellen Verlaufseintrag nicht ersetzen. Daher war es nicht möglich, zu einer neuen Seite zu navigieren, ohne dem Back-Stack einen Eintrag hinzuzufügen.
  • Entkoppelte Callbacks:Es gab keinen direkten Mechanismus, um einen bestimmten loadUrl-Aufruf mit nachfolgenden Callback-Ereignissen in WebViewClient zu korrelieren.
  • Zusätzliche Header nicht gespeichert:Benutzerdefinierte Header, die an loadUrl übergeben wurden, wurden nicht als Teil des WebView-Status gespeichert. Sie gingen also beim Wiederherstellen des Status verloren.

Die WebViewCompat.navigate API behebt diese Probleme durch die Einführung der folgenden Funktionen:

  • Ersetzen von Navigationsverlaufseinträgen:Ermöglicht das Ersetzen der aktuellen Seite im WebView-Verlaufsstack.
  • Korreliertes Callback-Tracking: Gibt ein Navigation-Objekt zurück, das als eindeutige ID für alle Phasen eines Navigationslebenszyklus dient.
  • Unterstützung für gespeicherte Statusheader:Zusätzliche Header werden zuverlässig im WebView-Statusbundle gespeichert, sodass sie bei der Statuswiederherstellung wiederverwendet werden können.

Wichtige Funktionen und Einschränkungen

Beachten Sie vor der Einführung von WebViewCompat.navigate die folgenden Betriebsregeln und Einschränkungen:

  • Threadsicherheit:Sie müssen WebViewCompat.navigate im UI-Thread (Hauptthread) aufrufen.

  • Abbruch und Vorrang:Aktive Navigationen können nicht explizit abgebrochen werden. Wenn Sie jedoch einen neuen navigate-Aufruf für dasselbe WebView initiieren, wird jede aktive Navigation überschrieben.

  • Unterstützung für URI-Schemas:Standard- (z. B. https: und http:) und benutzerdefinierte URI-Schemas werden unterstützt. Das javascript:-Schema wird nicht unterstützt.

  • URL-Größenbeschränkung:Die maximal unterstützte Länge des URL-Strings beträgt 2 MB.

  • Funktionsprüfung: Prüfen Sie immer die Verfügbarkeit von Funktionen mit WebViewFeature.isFeatureSupported, bevor Sie die API aufrufen, um die Kompatibilität mit verschiedenen WebView-APK-Versionen zu gewährleisten.

Navigation initiieren und Lebenszyklus verfolgen

So konfigurieren Sie die Navigation und verfolgen ihren Lebenszyklus:

  1. Registrieren Sie während der WebView Einrichtung eine NavigationListener Implementierung mit WebViewCompat.addNavigationListener, um strukturierte Lebenszyklus-Callbacks zu erhalten. Registrieren Sie den Listener einmal (nicht bei jedem Navigationsaufruf), um Speicherlecks und doppelte Callback-Ausführungen zu vermeiden.
  2. Erstellen Sie mit NavigationParameters.Builder eine NavigationParameters-Instanz, um optionale Verhaltensweisen wie das Ersetzen des Verlaufs oder benutzerdefinierte HTTP-Header anzugeben.
  3. Rufen Sie WebViewCompat.navigate auf und übergeben Sie Ihre WebView Instanz, die Ziel-URL und die Parameter.

WebViewCompat.navigate gibt ein Navigation-Objekt zurück, das die Anfrage eindeutig identifiziert. Vergleichen Sie dieses Objekt in Ihren NavigationListener-Callbacks mit dem eingehenden Navigation-Parameter, um diese bestimmte Navigation zu verfolgen.

Implementierungsbeispiel

Im folgenden Beispiel wird gezeigt, wie Sie Navigationsparameter konfigurieren, WebViewCompat.navigate aufrufen und auf Ereignisse im Navigationslebenszyklus warten:

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

Fehlermodi und Fehlerbehandlung

Die WebViewCompat.navigate API bietet verschiedene Mechanismen zur Behandlung von Konfigurationsfehlern und Laufzeitfehlern bei der Navigation:

Ausnahmen für ungültige Argumente

Wenn Sie ungültige Argumente übergeben, wird eine synchrone IllegalArgumentException ausgelöst. Häufige Ursachen sind:

  • null für erforderliche Parameter, die nicht null sein dürfen, übergeben (webView, url oder params).
  • Ein nicht unterstütztes URL-Schema angeben, z. B. javascript:.
  • Falsch formatierte HTTP-Headerschlüssel oder -werte übergeben, die nicht den RFC 2616-Spezifikationen entsprechen.

Wenn während der Netzwerkanfrage oder des Seitenaufbaus ein Fehler auftritt (z. B. ein HTTP 404 Statuscode, ein Fehler bei der DNS-Auflösung oder ein SSL-Fehler), gibt WebViewCompat.navigate trotzdem ein gültiges Navigation-Objekt zurück.

Wenn die Navigation abgeschlossen ist, prüfen Sie die folgenden Methoden für die Navigation Instanz in Ihrem onNavigationCompleted Callback, um den Fehler zu diagnostizieren:

  • getStatusCode: Gibt den HTTP-Antwortstatuscode zurück (z. B. 404 oder 500).
  • getWebResourceError: Gibt ein WebResourceErrorCompat Objekt zurück, das Netzwerkfehler wie Verbindungszeitüberschreitungen oder Fehler bei der Hostsuche beschreibt.
  • didCommitErrorPage: Gibt an, ob WebView eine Fehlerseite an den Nutzer gesendet und angezeigt hat.
  • didCommit: Gibt an, ob die Navigation erfolgreich auf einer Zielseite ausgeführt wurde, ohne abgebrochen zu werden.

Statusbundle-Verwaltung speichern

Wenn Sie zusätzliche Header mit NavigationParameters übergeben, speichert WebView diese Header im Statusbundle, sodass sie bei der Statuswiederherstellung wiederverwendet werden können. Große Header-Sammlungen können jedoch die Größe des gespeicherten Status-Bundle erheblich erhöhen.

Wenn Sie die Bundle-Größe beschränken müssen, um TransactionTooLargeException beim Speichern des Android-Status zu vermeiden, verwenden Sie WebViewCompat.saveState. Mit dieser Methode können Sie eine maximale Bundle-Größe in Byte festlegen und optional Elemente aus dem Vorwärtsverlauf ausschließen:

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

Das resultierende Bundle ist weiterhin mit der Standardmethode WebView.restoreState kompatibel.

Empfehlungen für die Migration und Implementierung

Folgen Sie diesen Empfehlungen, um eine optimale Leistung und Stabilität bei der Navigation in WebView zu gewährleisten:

  • Von loadUrl zu navigate migrieren:Migrieren Sie alle Legacy- WebView.loadUrl-Aufrufe zu WebViewCompat.navigate. So wird eine einheitliche Verlaufsverwaltung gewährleistet und Header werden immer als Teil des gespeicherten Status gespeichert.

  • Immer die Funktionsunterstützung prüfen:Prüfen Sie vor dem Aufrufen der API die Laufzeitunterstützung mit WebViewFeature.isFeatureSupported, um Probleme mit älteren WebView-Versionen zu vermeiden.

  • Navigationsinstanzen korrelieren:Verwenden Sie das zurückgegebene Navigation-Objekt, um gleichzeitige Navigationen zu unterscheiden oder Callbacks zu filtern, wenn Sie mehrere WebView-Instanzen verwalten.

  • Listener einmal bei der Initialisierung registrieren: Da WebViewCompat.addNavigationListener einen Listener hinzufügt, anstatt einen vorhandenen zu ersetzen, registrieren Sie Ihren NavigationListener einmal während der WebView -Einrichtung, um Speicherlecks und doppelte Callback-Ausführungen bei nachfolgenden Navigationen zu vermeiden.

  • Größe des gespeicherten Status überwachen:Wenn Sie große Header-Nutzlasten übergeben, verwenden Sie WebViewCompat.saveState mit expliziten Größenbeschränkungen, um zu vermeiden, dass zu viele Statusdaten gespeichert werden.

Zusätzliche Ressourcen

Weitere Informationen zu eingebetteten Webfunktionen und zur Leistungsoptimierung finden Sie in den folgenden Leitfäden: