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:
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 restituirenull. Se restituisci unWebResourceResponsepersonalizzato, 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
CancellationSignalper 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 campohttpStatusCode(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
IllegalArgumentExceptionsincrono.
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:
- Eseguire il debug delle app web
- Ottimizzare l'avvio di WebView
- Gestire l'interruzione del processo di rendering di WebView