Spekulatives Laden in WebView

Die Navigationslatenz ist ein wichtiger Messwert für die Nutzerfreundlichkeit. Damit Entwickler diese Latenz reduzieren können, bietet WebView APIs für das spekulative Laden. So kann Ihre App Inhalte abrufen oder rendern, bevor der Nutzer explizit dorthin navigiert.

WebView unterstützt drei Haupttypen des spekulativen Ladens: Preconnect, Prefetch und Prerender.

Durch die Implementierung einer Strategie für das spekulative Laden können Sie Folgendes erreichen:

  • Deutliche Reduzierung der Latenz beim Laden von Webinhalten:Die Startzeit des Netzwerks wird früher im Lebenszyklus der App festgelegt.
  • Höhere Erfolgsraten bei der Navigation:Durch das Vorab-Aufwärmen des Netzwerks und des Caches ist die Wahrscheinlichkeit geringer, dass die Navigation aufgrund vorübergehender Netzwerkprobleme fehlschlägt.
  • Bessere wahrgenommene Reaktionsfähigkeit:Insbesondere das Vorab-Rendern ermöglicht sofortige Übergänge, wodurch sich die App deutlich schneller anfühlt.

Strategie für das spekulative Laden auswählen

Der Hauptunterschied zwischen diesen Strategien liegt in ihrem Umfang: Die Preconnect API ist ursprungsbasiert, d. h., sie erfordert nur die Zieldomain. Die Prefetch- und Prerender-APIs sind URL-basiert, d. h., sie erfordern den genauen Pfad der Webseite.

Da Preconnect auf Ursprungsebene funktioniert, kann es viel früher im Lebenszyklus der App initiiert werden, noch bevor Sie wissen, zu welchen Inhalten oder zu welcher Seite der Nutzer navigieren wird.

In der folgenden Tabelle werden diese drei Strategien verglichen, damit Sie die richtige für Ihren Anwendungsfall auswählen können:

Funktion Preconnect Prefetch Prerender
Hauptziel Verbindung aufwärmen Nur HTML im Cache speichern (ohne JavaScript oder CSS) Gesamte Seite vorab rendern
Einsatzbereich Profilebene (für alle WebViews freigegeben) Profilebene (für alle WebViews freigegeben) WebView-Ebene (an eine bestimmte WebView gebunden)
Jetpack WebKit API androidx.webkit.Profile androidx.webkit.Profile androidx.webkit.WebViewCompat
Wichtige API-Methoden preconnect(...) prefetchUrlAsync(...) prerenderUrlAsync(...)
Konfiguration PrefetchCache.setMaxPrefetches()
PrefetchCache.setPrefetchTtlSeconds()
setMaxPrerenders()
Ressourcennutzung Niedrig (Netzwerk) Medium (Netzwerk, Arbeitsspeicher) Hoch (CPU, Arbeitsspeicher, Netzwerk)
Verwendung Wenn der Zielursprung bekannt ist, die genaue URL aber noch nicht festgelegt wurde. Wenn die genaue URL bekannt ist und die Navigation wahrscheinlich ist, wobei der Cache für alle WebViews freigegeben ist. Wenn die genaue URL bekannt ist und die Navigation innerhalb einer bestimmten WebView sehr wahrscheinlich ist.
Vorteile Schnellere Verbindungseinrichtung für jede URL im Ursprung Schnellere Netzwerklast für übereinstimmende URLs Wirklich sofortige Navigation nach der Aktivierung

Vorverbindung zu Ursprüngen herstellen

Durch die Vorverbindung werden zukünftige Ladevorgänge beschleunigt, indem DNS-Lookups und TCP/TLS-Handshakes für einen bestimmten Ursprung vorab ausgeführt werden.

Im Gegensatz zu Prefetch und Prerender, die eine genaue Ziel-URL erfordern, ist Preconnect streng ursprungsbasiert. So können Sie den Preconnect-Aufruf viel früher als Prefetch und Prerender ausführen.

Diese ressourcenschonende Strategie auf Profilebene reduziert die anfängliche Latenz für jede WebView, die dieses Profil verwendet, sofern der Ursprung noch nicht besucht wurde. Die Verbindung bleibt etwa 30 Sekunden lang offen. Das ist von Vorteil für alle nachfolgenden ursprungsübergreifenden HTTP-Anfragen, Navigationen oder Unterressourcen, da der Handshake-Overhead entfällt.

Implementierung

Rufen Sie preconnect(String url) in einer Profile-Instanz auf, um eine Vorverbindung zu initiieren. Diese API muss im UI-Thread aufgerufen werden und erfordert, dass WebViewFeature.PRECONNECT unterstützt wird.

Die API funktioniert auf Ursprungsebene. Zur Vereinfachung kann jedoch eine vollständige URL angegeben werden (z. B. https://www.example.com/index.html). Diese wird automatisch als Aufruf des Ursprungs behandelt (z. B. https://www.example.com). Es können mehrere Ursprünge verbunden werden, indem diese API mehrmals aufgerufen wird.

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
}

Allgemeine Konfiguration: PrefetchParameters und PrerenderParameters

Sowohl Prefetch als auch Prerender verwenden PrefetchParameters oder PrerenderParameters, um die Anfrage anzupassen. Mit diesen Klassen können Sie zusätzliche Header und Hinweise für den URL-Abgleich angeben, z. B. No-Vary-Search Konfigurationen.

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

Inhalte per Prefetch abrufen

Beim Prefetch wird die Haupt-HTML-Ressource einer URL heruntergeladen und im Netzwerkcache des Profils gespeichert. In WebView dient ein Profile als Container für Browserdaten, einschließlich Cookies, HTTP-Cache und Service Workern. Da der Prefetch ein Vorgang auf Profilebene ist, kann jede WebView, die mit diesem Profil verknüpft ist, die im Cache gespeicherte Antwort nutzen.

Implementierung

Rufen Sie prefetchUrlAsync() in einer Profile-Instanz auf, um einen Prefetch zu initiieren. Dieser Vorgang unterstützt nur das HTTPS-Schema.

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

Lebenszyklus der Abfrage

Die WebView-Prefetch-Anfrage ändert, wann und wie der shouldInterceptRequest()-Callback ausgelöst wird. Da dies einen direkten Einfluss darauf hat, ob Ihre per Prefetch abgerufenen Inhalte erfolgreich verwendet werden, ist es wichtig, den zweistufigen Lebenszyklus zu verstehen:

Diagramm mit dem Lebenszyklus des Abfangens von WebView-Prefetching in zwei Schritten während der spekulativen Phase und der Navigationsphase.
Abbildung 1. Der zweistufige Lebenszyklus der Abfrage für WebView-Prefetch Anfragen und Navigationen.

1. Die spekulative Phase (Prefetch-Anfrage)

Wenn prefetchUrlAsync() aufgerufen wird, lädt WebView die Haupt-HTML-Ressource im Hintergrund herunter. shouldInterceptRequest() wird für diese Hintergrundanfrage vollständig übersprungen. Benutzerdefinierte Logik, Autorisierungstokens oder Header-Einfügungen, die normalerweise in Ihrem Interceptor verarbeitet werden, werden nicht auf die per Prefetch abgerufene HTML-Ressource angewendet.

2. Die Navigationsphase (Nutzeraktivierung)

Wenn die App explizit zur URL navigiert (z. B. mit WebViewCompat.navigate oder loadUrl) oder der Nutzer auf einen entsprechenden Link klickt, ermittelt WebView, ob der per Prefetch abgerufene Cache verwendet werden kann:

  • Haupt-HTML-Auswertung:WebView löst shouldInterceptRequest() für das Haupt-HTML aus. Damit die Seite erfolgreich aus dem Prefetch-Cache bereitgestellt werden kann, muss Ihr Interceptor null zurückgeben. Wenn Sie eine benutzerdefinierte WebResourceResponse zurückgeben, berücksichtigt WebView Ihren Interceptor und umgeht den Prefetch-Cache vollständig.

  • Auswertung von Unterressourcen:Nachdem das per Prefetch abgerufene HTML zur Verwendung freigegeben wurde, wird shouldInterceptRequest() normal für alle nachfolgenden Unterressourcen (z. B. Bilder, Skripts und CSS) ausgelöst, die zum Rendern der Seite erforderlich sind.

Wichtigste Merkmale

Die folgenden Betriebsmerkmale und Berechtigungsprüfungen bestimmen, wie WebView Prefetch-Anfragen initiiert und verwaltet:

  • Threadsicherheit:Anfragen können von jedem Thread aus initiiert werden.
  • Berechtigung:Bevor ein Abruf initiiert wird, prüft WebView, ob die Anfrage sicher und kontextuell angemessen ist. Dazu werden folgende Prüfungen durchgeführt:
    • Vorhandene Cookies:Zum Schutz der Privatsphäre der Nutzer und zur Vermeidung von CSRF-ähnlichen Nebeneffekten überspringt WebView möglicherweise den Prefetch, wenn für die Anfrage bestimmte authentifizierte Cookies erforderlich sind, die eine Zustandsänderung auf dem Server auslösen könnten.
    • Vorhandensein von Service Workern:Wenn ein Service Worker bereits den Umfang der URL steuert, kann WebView den Abruf-Handler des Service Workers verwenden, anstatt einen Standard-Netzwerk-Prefetch zu initiieren.
    • Verfügbarkeit des Proxys:WebView prüft, ob der aktuelle Netzwerkpfad (einschließlich aller konfigurierten Proxys) stabil ist, um zu vermeiden, dass spekulative Anfragen bei komplexen Netzwerkkonfigurationen fehlschlagen.
  • Wenn ein Prefetch nicht gestartet werden kann (auch mit gültigen Parametern), liegt das oft daran, dass WebView festgestellt hat, dass eine Hintergrundanfrage die aktuelle Sitzung oder den Sicherheitsstatus des Nutzers beeinträchtigen könnte.
  • Abbrechen:Verwenden Sie CancellationSignal, um eine laufende Anfrage zu beenden und zu verhindern, dass sie im Cache gespeichert wird.

Seiten vorab rendern

Beim Vorab-Rendern werden verborgene Webinhalte erstellt, um eine Seite vollständig im Hintergrund zu rendern, einschließlich der Skriptausführung und des Abrufs von Unterressourcen. Das Vorab-Rendern basiert auf derselben zugrunde liegenden Infrastruktur wie der Prefetch. Wenn eine App einen Prerender initiiert, führt WebView zuerst einen Prefetch der Antwort aus, um die Prerender-Navigation zu ermöglichen und redundante Netzwerkaktivitäten zu vermeiden.

Implementierung

Das Vorab-Rendern ist ein Vorgang auf WebView-Instanzebene. Rufen Sie prerenderUrlAsync() mit WebViewCompat aus dem UI-Thread auf.

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

Sowohl Prefetch als auch Prerender sind vollständig asynchron. prefetchUrlAsync() kann von jedem Thread aus aufgerufen werden, während prerenderUrlAsync() vom UI-Thread aus initiiert werden muss.

Technische Einschränkungen

Um eine sofortige Navigation mit der Systemgesundheit in Einklang zu bringen, erzwingt WebView die folgenden Laufzeiteinschränkungen:

  • Arbeitsspeicherbelastung:WebView bricht das Vorab-Rendern von URLs ab, wenn auf dem Gerät wenig Arbeitsspeicher verfügbar ist.
  • Nicht zulässige APIs:Alle Versuche von JavaScript, im Hintergrund auf bestimmte APIs zuzugreifen (z. B. Audiowiedergabe, Benachrichtigungen), beenden das Vorab-Rendern sofort.
  • Instanzlimit:Die Anzahl der aktiven vorab gerenderten URLs pro WebView ist begrenzt.

URL-Abgleich und No-Vary-Search (NVS)

WebView erfordert einen zuverlässigen Abgleichalgorithmus, um sicherzustellen, dass eine vorab geladene Ressource nur für die beabsichtigte Navigation bereitgestellt wird.

Exakter Abgleich im Vergleich zum NVS-Abgleich

Standardmäßig erfordern Prefetch und Prerender einen exakten URL-Abgleich. Wenn die navigierte URL mit der vorab geladenen URL identisch ist, wird sie sofort aus dem Cache bereitgestellt. Wenn sich die Abfrageparameter unterscheiden, verwendet WebView die folgenden No-Vary-Search-Regeln (NVS):

  • Der Hinweis:Entwickler geben bei der Initiierung einen setExpectedNoVarySearchHeader()-Hinweis an. Wenn die navigierte URL mit der Anfrage-URL abzüglich der angegebenen Parameter übereinstimmt, blockiert WebView kurz, um auf die tatsächlichen Header des Servers zu warten.
  • Server-Header:Der NVS-Antwortheader vom Server ist die endgültige Autorität. Wenn der Server bestätigt, dass Abfrageunterschiede ignoriert werden sollen, wird der Abgleich aus dem Cache bereitgestellt. Andernfalls greift WebView auf einen Kaltstart des Netzwerks zurück.

No-Vary-Search (NVS) ist für die erweiterte Verwendung vorgesehen und die meisten Entwickler benötigen es möglicherweise nicht , da sie sowohl für den Prefetch als auch für die Navigation (WebViewCompat.navigate oder loadUrl) genau dieselbe URL übergeben. Diese Anleitung ist nur erforderlich, wenn es Unterschiede bei den Suchparametern zwischen der Prefetch-URL und der navigierten URL gibt.

Globale Konfiguration

Sie können das Verhalten beim spekulativen Laden auf Profilebene anpassen, indem Sie die PrefetchCache-Limits und die maximale Anzahl von Vorab-Rendern konfigurieren. Sie können benutzerdefinierte Prefetch-Limits auch auf die Standardeinstellungen des Systems zurücksetzen:

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

Fehlerbehandlung und Ausnahmen

Bei spekulativen Vorgängen werden OutcomeReceiverCompat oder PrerenderOperationCallback verwendet, um Ergebnisse zu melden.

Hauptausnahmen

Wenn ein spekulatives Laden fehlschlägt, meldet Ihr Fehlerhandler einen der folgenden Hauptausnahmetypen, damit Sie bestimmte Fehlerszenarien diagnostizieren können:

  • PrefetchException:Die Basisklasse für alle asynchronen Prefetch-Fehler.
  • PrefetchNetworkException:Gibt einen Fehler auf Netzwerk- oder Serverebene an. Sie kann ein httpStatusCode-Feld (z. B. 404 oder 503) enthalten, um serverseitige Probleme zu diagnostizieren.
  • PrerenderException:Die Basisklasse für alle Fehler im Zusammenhang mit dem Vorab-Rendern, z. B. Fehler aufgrund von Arbeitsspeicherbelastung oder der Verwendung nicht zulässiger APIs (z. B. Audiowiedergabe) im Hintergrund.

Optimierungsstrategien

Folgen Sie diesen Empfehlungen, um die Vorteile des spekulativen Ladens zu maximieren und gleichzeitig Systemressourcen zu sparen:

  • Frühzeitig initiieren:Starten Sie den Prefetch beim Start der App oder sobald ein Navigationsziel wahrscheinlich ist.
  • Integrierte Strategie:Wenn Sie eine URL vorab rendern, die sich bereits im Prefetch-Cache befindet, wird die Prerender-Navigation aus diesem Cache bereitgestellt. So werden redundante Netzwerkanfragen vermieden.
  • Kontingente im Blick behalten:Das Vorab-Rendern ist ressourcenintensiv. Bevorzugen Sie den Prefetch für mehrere wahrscheinliche Kandidaten und reservieren Sie das Vorab-Rendern für die wahrscheinlichste Navigation.
  • Schemaunderstützung:Alle URLs müssen das obligatorische HTTPS-Schema verwenden. Ungültige Schemas oder Null-Eingaben lösen eine synchrone IllegalArgumentException aus.

Zusätzliche Ressourcen

Weitere Informationen zur Fehlerbehebung bei Web-Apps, zur Optimierung der Startleistung von WebView und zur Verarbeitung der Beendigung des Renderer-Prozesses finden Sie in den folgenden Ressourcen: