Caricamento speculativo in WebView

La latenza di navigazione è una metrica fondamentale per l'esperienza utente. Per aiutare gli sviluppatori a ridurre questa latenza, WebView fornisce API per il caricamento speculativo, consentendo alla tua app di recuperare o eseguire il rendering dei contenuti prima che l'utente li raggiunga esplicitamente.

WebView supporta tre tipi principali di caricamento speculativo: Preconnect, Prefetch e Prerender.

Implementando una strategia di caricamento speculativo, puoi ottenere i seguenti risultati:

  • Riduzione significativa della latenza di caricamento dei contenuti web:sposta l'ora di inizio della rete in un momento precedente del ciclo di vita dell'app.
  • Tassi di successo della navigazione più elevati:preriscaldando la rete e la cache, è meno probabile che le navigazioni non riescano a causa di problemi di rete temporanei.
  • Migliore reattività percepita:il prerendering, in particolare, consente transizioni istantanee che rendono l'app molto più veloce.

Scegliere una strategia di caricamento speculativo

La principale differenza tra queste strategie risiede nel loro ambito: l'API Preconnect è basata sull'origine, il che significa che richiede solo il dominio di destinazione. Le API Prefetch e Prerender sono basate su URL, il che significa che richiedono il percorso esatto della pagina web.

Poiché Preconnect opera a livello di origine, può essere avviato molto prima nel ciclo di vita dell'app, anche prima di conoscere i contenuti o la pagina specifici a cui l'utente accederà.

La seguente tabella confronta queste tre strategie per aiutarti a scegliere quella più adatta al tuo caso d'uso:

Funzionalità Preconnect Precaricamento Prerender
Obiettivo principale Riscaldare la connessione Memorizza nella cache solo l'HTML (senza JavaScript o CSS) Pre-rendering dell'intera pagina
Ambito Livello di profilo (condiviso tra le WebView) Livello di profilo (condiviso tra le WebView) Livello WebView (associato a una WebView specifica)
API Jetpack WebKit androidx.webkit.Profile androidx.webkit.Profile androidx.webkit.WebViewCompat
Metodi API principali preconnect(...) prefetchUrlAsync(...) prerenderUrlAsync(...)
Configurazione N/D PrefetchCache.setMaxPrefetches()
PrefetchCache.setPrefetchTtlSeconds()
setMaxPrerenders()
Utilizzo delle risorse Bassa (rete) Medio (rete, memoria) Alto (CPU, memoria, rete)
Quando si usa Quando l'origine di destinazione è nota, ma l'URL specifico non è ancora stato determinato. Quando l'URL esatto è noto e la navigazione è probabile, con la memorizzazione nella cache condivisa tra le WebView. Quando l'URL esatto è noto e la navigazione è altamente certa all'interno di una WebView specifica.
Vantaggi Configurazione più rapida della connessione per qualsiasi URL sull'origine Caricamento più rapido della rete per gli URL corrispondenti Navigazione davvero istantanea all'attivazione

Precollegati alle origini

La pre-connessione velocizza i caricamenti futuri eseguendo in modo preventivo ricerche DNS e handshake TCP/TLS per un'origine specificata.

A differenza di Prefetch e Prerender, che richiedono un URL di destinazione esatto, Preconnect si basa rigorosamente sull'origine. In questo modo, puoi effettuare la chiamata Preconnect molto prima di Prefetch e Prerender.

Questa strategia a basso consumo di risorse a livello di profilo riduce la latenza iniziale per qualsiasi condivisione di WebView di questo profilo, a condizione che l'origine non sia già stata visitata. La connessione rimane aperta per circa 30 secondi, a vantaggio di eventuali richieste HTTP multiorigine, navigazioni o risorse secondarie successive, eliminando l'overhead dell'handshake.

Implementazione

Per avviare una pre-connessione, chiama preconnect(String url) su un'istanza Profile. Questa API deve essere chiamata sul thread dell'interfaccia utente e richiede il supporto di WebViewFeature.PRECONNECT.

L'API opera sull'origine, ma per comodità è possibile fornire un URL completo (ad esempio https://www.example.com/index.html). Questo viene trattato automaticamente come una chiamata all'origine (ad esempio, https://www.example.com). È possibile connettersi a più origini chiamando questa API più volte.

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
}

Configurazione comune: PrefetchParameters e PrerenderParameters

Sia il precaricamento che il prerendering utilizzano PrefetchParameters o PrerenderParameters per personalizzare la richiesta. Queste classi ti consentono di fornire intestazioni e suggerimenti aggiuntivi per la corrispondenza degli URL, ad esempio le configurazioni No-Vary-Search.

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

Recupero anticipato dei contenuti

Il precaricamento scarica la risorsa HTML principale di un URL e la memorizza nella cache di rete del profilo. In WebView, un Profile funge da contenitore per i dati del browser, inclusi cookie, cache HTTP e service worker. Poiché il recupero preventivo è un'operazione a livello di profilo, qualsiasi WebView associato a quel profilo può sfruttare la risposta memorizzata nella cache.

Implementazione

Per avviare un prefetch, chiama prefetchUrlAsync() su un'istanza Profile. Questa operazione supporta solo lo schema HTTPS.

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

Ciclo di vita dell'intercettazione

La richiesta di precaricamento di WebView modifica quando e come viene attivato il callback shouldInterceptRequest(). Poiché questo ha un impatto diretto sull'utilizzo dei contenuti precaricati, è fondamentale comprendere il ciclo di vita in due fasi:

Diagramma che mostra il ciclo di vita dell'intercettazione del prefetch di WebView in due passaggi
  durante le fasi speculativa e di navigazione.
Figura 1. Il ciclo di vita dell'intercettazione in due passaggi per le richieste e le navigazioni di prefetch di WebView.

1. Fase speculativa (richiesta di precaricamento)

Quando viene richiamato prefetchUrlAsync(), WebView scarica in background la risorsa HTML principale. shouldInterceptRequest() viene completamente ignorato per questa richiesta in background. Qualsiasi logica personalizzata, token di autorizzazione o inserimento di intestazioni gestiti in genere all'interno dell'intercettore non vengono applicati alla risorsa HTML precaricata.

2. La fase di navigazione (attivazione utente)

Quando l'app passa esplicitamente all'URL (ad esempio, utilizzando loadUrl()) o l'utente fa clic su un link corrispondente, WebView determina se può utilizzare la cache precaricata:

  • Valutazione HTML principale:WebView attiverà shouldInterceptRequest() per l'HTML principale in questo momento. Per pubblicare correttamente la pagina dalla cache prefetch, l'intercettatore deve restituire null. Se restituisci un WebResourceResponse personalizzato, WebView rispetta l'intercettore e ignora completamente la cache di precaricamento.

  • Valutazione delle risorse secondarie:dopo che l'HTML precaricato è stato autorizzato all'uso, shouldInterceptRequest() viene attivato normalmente per tutte le risorse secondarie successive (ad esempio immagini, script e CSS) necessarie per completare il rendering della pagina.

Comportamenti principali

Le seguenti caratteristiche operative e controlli di idoneità regolano il modo in cui WebView avvia e gestisce le richieste di precaricamento:

  • Thread safety:le richieste possono essere avviate da qualsiasi thread.
  • Idoneità:prima di avviare un recupero, WebView si assicura che la richiesta sia sicura e contestualmente appropriata controllando quanto segue:
    • Cookie esistenti:per proteggere la privacy degli utenti e prevenire effetti collaterali simili a CSRF, WebView potrebbe saltare il prefetching se la richiesta richiede cookie autenticati specifici che potrebbero attivare una modifica dello stato sul server.
    • Presenza di service worker:se un service worker controlla già l'ambito dell'URL, WebView può rimandare al gestore di recupero del service worker anziché avviare un precaricamento di rete standard.
    • Disponibilità del proxy:WebView verifica che il percorso di rete corrente (inclusi i proxy configurati) sia stabile per evitare errori nelle richieste speculative in configurazioni di rete complesse.
  • Se il prefetch non viene avviato (anche con parametri validi), spesso è perché WebView ha stabilito che una richiesta in background potrebbe interferire con la sessione corrente o lo stato di sicurezza dell'utente.
  • Annullamento:utilizza CancellationSignal per terminare una richiesta in corso ed evitare che venga memorizzata nella cache.

Rendering preliminare delle pagine

Il prerendering crea "contenuti web" nascosti per eseguire il rendering completo di una pagina in background, inclusa l'esecuzione di script e il recupero di risorse secondarie. Il prerendering si basa sulla stessa infrastruttura sottostante del prefetching. Se un'app avvia un prerendering, WebView esegue prima un prefetch della risposta per gestire la navigazione prerender, evitando attività di rete ridondanti.

Implementazione

Il prerendering è un'operazione a livello di istanza WebView. Chiama prerenderUrlAsync() utilizzando WebViewCompat dal thread dell'interfaccia utente.

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

Sia il prefetch che il prerendering sono completamente asincroni. prefetchUrlAsync() può essere chiamato da qualsiasi thread, mentre prerenderUrlAsync() deve essere avviato dal thread dell'interfaccia utente.

Vincoli tecnici

Per bilanciare la navigazione istantanea con l'integrità del sistema, WebView applica i seguenti vincoli di runtime:

  • Pressione della memoria: WebView annulla gli URL pre-renderizzati se il dispositivo ha poca RAM.
  • API non consentite:qualsiasi tentativo di JavaScript di accedere a determinate API (ad esempio, riproduzione audio, avvisi) in un contesto in background interromperà immediatamente il prerendering.
  • Limite di istanze:esiste un limite al numero di URL prerenderizzati attivi consentiti per WebView.

Corrispondenza degli URL e No-Vary-Search (NVS)

WebView richiede un algoritmo di corrispondenza affidabile per garantire che una risorsa precaricata venga pubblicata solo per la navigazione prevista.

Corrispondenza esatta e corrispondenza NVS

Per impostazione predefinita, il precaricamento e il prerendering richiedono una corrispondenza esatta dell'URL. Se l'URL visitato è identico all'URL precaricato, viene pubblicato immediatamente dalla cache. Se i parametri di query sono diversi, WebView utilizza le seguenti regole No-Vary-Search (NVS):

  • Il suggerimento: gli sviluppatori forniscono un suggerimento setExpectedNoVarySearchHeader() durante l'avvio. Se l'URL visitato corrisponde all'URL della richiesta meno i parametri suggeriti, WebView si blocca brevemente per attendere le intestazioni effettive del server.
  • Intestazione del server: l'intestazione della risposta NVS dal server è l'autorità definitiva. Se il server conferma che le differenze nella query devono essere ignorate, la corrispondenza viene pubblicata dalla cache. In caso contrario, WebView esegue un caricamento di rete freddo.

No-Vary-Search (NVS) è per un utilizzo avanzato e la maggior parte degli sviluppatori potrebbe non averne bisogno perché trasmette esattamente lo stesso URL sia al precaricamento sia a loadUrl(). Queste indicazioni sono necessarie solo se esistono differenze nei parametri di query tra l'URL di prefetch e l'URL loadUrl().

Configurazione globale

Ottimizza il comportamento di caricamento speculativo a livello di profilo configurando i limiti PrefetchCache e il numero massimo di prerender. Puoi anche ripristinare i limiti di precaricamento personalizzati ai valori predefiniti del sistema:

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

Gestione degli errori ed eccezioni

Le operazioni speculative utilizzano OutcomeReceiverCompat o PrerenderOperationCallback per segnalare i risultati.

Eccezioni principali

Quando un'operazione di caricamento speculativo non riesce, il gestore degli errori segnala uno dei seguenti tipi di eccezione principali per aiutarti a diagnosticare scenari di errore specifici:

  • PrefetchException: la classe base per tutti gli errori di prefetch asincrono.
  • PrefetchNetworkException: indica un errore a livello di rete o server. Può includere un campo httpStatusCode (ad esempio 404 o 503) per facilitare la diagnosi dei problemi lato server.
  • PrerenderException: la superclasse per tutti gli errori correlati al prerendering, come gli errori dovuti alla pressione della memoria o all'utilizzo di API non consentite (come la riproduzione audio) in background.

Strategie di ottimizzazione

Segui questi consigli per massimizzare i vantaggi del caricamento speculativo e risparmiare risorse di sistema:

  • Avvia in anticipo:avvia il prefetching durante l'avvio dell'app o non appena è probabile una destinazione di navigazione.
  • Strategia integrata: se esegui il prerendering di un URL già presente nella cache di prefetch, la navigazione prerenderizzata viene pubblicata da questa cache, evitando richieste di rete ridondanti.
  • Monitora le quote:il prerendering richiede molte risorse. Preferisci il prefetching per diversi candidati probabili e riserva il prerendering per la navigazione più probabile.
  • Supporto dello schema:assicurati che tutti gli URL utilizzino lo schema HTTPS obbligatorio. Schemi non validi o input nulli attivano un IllegalArgumentException sincrono.

Risorse aggiuntive

Per scoprire di più sul debug delle app web, sull'ottimizzazione delle prestazioni di avvio di WebView e sulla gestione dell'interruzione del processo di rendering, consulta le seguenti risorse: