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, che consentono 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 di navigazione più elevati: grazie al pre-riscaldamento della rete e della cache, è meno probabile che le navigazioni non vadano a buon fine a causa di problemi di rete temporanei.
- Migliore reattività percepita: il prerendering, in particolare, consente transizioni istantanee che rendono l'app notevolmente 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 sull'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 che l'utente raggiungerà.
La tabella seguente confronta queste tre strategie per aiutarti a scegliere quella giusta per il tuo caso d'uso:
| Funzionalità | Preconnect | Prefetch | Prerender |
|---|---|---|---|
| Obiettivo principale | Riscalda la connessione | Memorizza nella cache solo l'HTML (senza JavaScript o CSS) | Esegui il prerendering dell'intera pagina |
| Ambito | A livello di profilo (condiviso tra le WebView) | A livello di profilo (condiviso tra le WebView) | A livello di WebView (vincolato 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 | Basso (rete) | Medio (rete, memoria) | Alto (CPU, memoria, rete) |
| Quando si usa | Quando l'origine di destinazione è nota, ma l'URL specifico non è ancora 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 della connessione più rapida per qualsiasi URL sull'origine | Caricamento di rete più rapido per gli URL corrispondenti | Navigazione davvero istantanea all'attivazione |
Precollegamento alle origini
Il precollegamento velocizza i caricamenti futuri eseguendo in modo preventivo le ricerche DNS e gli handshake TCP/TLS per un'origine specificata.
A differenza di Prefetch e Prerender, che richiedono un URL di destinazione esatto, Preconnect è strettamente basato sull'origine. In questo modo puoi effettuare la chiamata Preconnect molto prima di Prefetch e Prerender.
Questa strategia a basso consumo di risorse e a livello di profilo riduce la latenza iniziale per qualsiasi WebView che condivide il 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, navigazioni o risorse secondarie multiorigine successive, eliminando il sovraccarico dell'handshake.
Implementazione
Per avviare un precollegamento, chiama preconnect(String url) su un'istanza Profile. Questa API deve essere chiamata sul thread dell'interfaccia utente e richiede che WebViewFeature.PRECONNECT sia supportato.
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 connettere 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 Prefetch sia Prerender 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();
Precaricamento 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 container per i dati del browser, inclusi cookie, cache HTTP e service worker. Poiché il precaricamento è un'operazione a livello di profilo, qualsiasi WebView associata a quel profilo può sfruttare la risposta memorizzata nella cache.
Implementazione
Per avviare un precaricamento, 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é ciò ha un impatto diretto sul fatto che i contenuti precaricati vengano utilizzati correttamente, è fondamentale comprendere il ciclo di vita in due passaggi:
1. La fase speculativa (richiesta di precaricamento)
Quando viene richiamato prefetchUrlAsync(), WebView scarica la risorsa HTML principale in background. shouldInterceptRequest() viene completamente ignorato per questa richiesta in background. La logica personalizzata, i token di autorizzazione o le iniezioni di intestazione gestite in genere all'interno dell'intercettatore non vengono applicati alla risorsa HTML precaricata.
2. La fase di navigazione (attivazione utente)
Quando l'app naviga esplicitamente all'URL (ad esempio utilizzando
WebViewCompat.navigate o loadUrl) o l'utente fa clic su un
link corrispondente, WebView determina se può utilizzare la cache precaricata:
Valutazione HTML principale: in questo momento WebView attiverà
shouldInterceptRequest()per l'HTML principale. Per pubblicare correttamente la pagina dalla cache di precaricamento, l'intercettatore deve restituirenull. Se restituisci unWebResourceResponsepersonalizzato, WebView rispetta l'intercettatore e ignora completamente la cache di precaricamento.Valutazione delle risorse secondarie: dopo che l'HTML precaricato è stato autorizzato per l'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 i 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 appropriata a livello di contesto controllando quanto segue:
- Cookie esistenti: per proteggere la privacy degli utenti e prevenire effetti collaterali simili a CSRF, WebView potrebbe saltare il precaricamento 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 eventuali proxy configurati) sia stabile per evitare che le richieste speculative non vadano a buon fine in configurazioni di rete complesse.
- Se l'avvio di un precaricamento non va a buon fine (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 e impedirne la memorizzazione nella cache.
Prerendering 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 precaricamento. Se un'app avvia un prerendering, WebView esegue prima un precaricamento della risposta per pubblicare la navigazione di prerendering, 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 precaricamento sia 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 da parte di JavaScript di accedere a determinate API (ad esempio, riproduzione audio, avvisi) in un contesto in background terminerà immediatamente il prerendering.
- Limite di istanze: esiste un limite al numero di URL pre-renderizzati 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 rispetto alla corrispondenza NVS
Per impostazione predefinita, il precaricamento e il prerendering richiedono una corrispondenza esatta dell'URL. Se l'URL raggiunto è 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 raggiunto 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 del server è l'autorità definitiva. Se il server conferma che le differenze di query devono essere ignorate, la corrispondenza viene pubblicata dalla cache. In caso contrario, WebView torna a un caricamento di rete freddo.
No-Vary-Search (NVS) è per un utilizzo avanzato e la maggior parte degli sviluppatori potrebbe non averne bisogno
perché passa esattamente lo stesso URL sia al precaricamento sia alla navigazione
(WebViewCompat.navigate o loadUrl). Queste indicazioni sono necessarie solo
se esistono differenze nei parametri di query tra l'URL di precaricamento
e l'URL raggiunto.
Configurazione globale
Ottimizza il comportamento di caricamento speculativo a livello di profilo configurando i limiti di PrefetchCache e il numero massimo di prerendering. Puoi anche reimpostare i limiti di precaricamento personalizzati sui 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 un OutcomeReceiverCompat o PrerenderOperationCallback per segnalare i risultati.
Eccezioni principali
Quando un'operazione di caricamento speculativo non va a buon fine, 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 precaricamento asincroni.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 relativi al prerendering, ad esempio 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 al contempo conservare le risorse di sistema:
- Avvia in anticipo: inizia il precaricamento 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 precaricamento, la navigazione di prerendering viene pubblicata da quella cache, evitando richieste di rete ridondanti.
- Monitora le quote: il prerendering richiede molte risorse. Preferisci il precaricamento 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. Gli schemi non validi o gli input nulli attivano un
IllegalArgumentExceptionsincrono.
Risorse aggiuntive
Per scoprire di più sul debug delle app web, sull'ottimizzazione del rendimento di avvio di WebView e sulla gestione della terminazione del processo di rendering, consulta le seguenti risorse:
- Navigazione nelle pagine migliorata con il metodo
WebViewCompat.navigate - Eseguire il debug delle app web
- Ottimizzare l'avvio di WebView
- Gestire la terminazione del processo di rendering di WebView