Quando la tua app utilizza per la prima volta un componente WebView, il sistema esegue attività di avvio specifiche.
Questa procedura di avvio è pesante. Per impostazione predefinita, si verifica implicitamente sul thread UI la prima volta che l'applicazione chiama molte API all'interno dei pacchetti android.webkit o androidx.webkit o gonfia un layout che contiene un tag WebView.
Perché è importante
Poiché questo avvio implicito avviene interamente sul thread principale, impedisce all'app di elaborare l'input dell'utente e aumenta drasticamente il rischio di errori ANR (L'applicazione non risponde). Per saperne di più su come Android gestisce il modello di esecuzione a thread singolo, consulta la Panoramica di processi e thread.
Trigger per l'avvio implicito
L'avvio implicito può essere attivato nei seguenti modi:
- A livello di programmazione: chiamando API come
WebSettings.getUserAgentString(). - Utilizzo dei layout: chiamata di
setContentView()olayoutInflater.inflate()su una risorsa XML che include un<WebView>.
L'avvio implicito può anche influire negativamente sulle metriche aziendali, come il tempo di avvio
dell'app e il tempo al primo display. Se l'inizializzazione implicita non è
ottimale per la tua app, utilizza startUpWebView.
Questa pagina illustra come ottimizzare le prestazioni di avvio di WebView utilizzando l'API startUpWebView.
Controllare l'avvio di WebView
Per migliorare le prestazioni e ridurre al minimo gli errori ANR, utilizza l'API startUpWebView disponibile
nella libreria Jetpack Webkit. Questa API ti offre il controllo esplicito sull'avvio di WebView. Sposta una quantità significativa del carico di lavoro di avvio su un
thread in background e consente di eseguire qualsiasi lavoro che deve essere eseguito sul thread dell'interfaccia utente
in blocchi, anziché in un unico blocco monolitico di grandi dimensioni. In questo modo, il thread
della UI può gestire in parallelo altre attività critiche dell'app, riducendo la possibilità di
bloccare l'esperienza utente.
L'API utilizza il callback androidx.webkit.WebViewOutcomeReceiver, che ti consente di
monitorare le inizializzazioni riuscite.
Per utilizzare questa API, aggiungi la libreria Jetpack Webkit al file build.gradle.
Assicurati di utilizzare la versione 1.16.0 o successive:
dependencies {
implementation("androidx.webkit:webkit:1.16.0")
}
Utilizzare l'API startUpWebView
Il modo in cui ottimizzi il flusso di avvio dipende dal momento in cui l'app deve effettivamente visualizzare la WebView.
Quando WebView non si trova nel percorso critico
Se la tua app non deve caricare immediatamente un componente WebView, puoi nascondere completamente il costo di inizializzazione. Chiama startUpWebView all'inizio del ciclo di vita
della tua app e attendi l'attivazione del callback di successo.
Idealmente, dovresti attendere il callback prima di chiamare altre API WebView. Se
attivi startUpWebView ma non aspetti che termini prima di toccare
altri componenti WebView, il sistema blocca il thread dell'interfaccia utente in attesa del
completamento dell'inizializzazione. La tua app potrebbe ottenere alcuni vantaggi in termini di prestazioni dal
lavoro in background già completato, ma non il massimo vantaggio.
Quando WebView si trova nel percorso critico
Se il percorso utente principale della tua app richiede immediatamente un componente WebView, probabilmente
non puoi permetterti di attendere il completamento dell'avvio di WebView. In questo scenario, devi comunque chiamare startUpWebView il prima possibile nel ciclo di vita dell'app (ad esempio in Application.onCreate), ma non aspettare che venga attivato il callback. Utilizza invece direttamente le API WebView quando sono necessarie.
Per ottenere il massimo vantaggio dall'avvio asincrono, rimanda l'istanza di una WebView o la chiamata alle API WebView finché non sono rimaste altre operazioni del thread dell'interfaccia utente del percorso critico da eseguire (come l'esecuzione dell'inflate delle gerarchie di layout, l'inizializzazione di altri SDK o il disegno del frame iniziale).
Se chiami startUpWebView e invochi immediatamente le API WebView sul thread principale, il thread dell'interfaccia utente si blocca in attesa del completamento dell'inizializzazione. In questo scenario, non ci sono vantaggi in termini di rendimento.
Se l'utilizzo di WebView può diventare un percorso critico, ma non vuoi avviare WebView completamente, puoi scegliere di eseguire selettivamente le attività di avvio di WebView che possono essere eseguite su un thread in background, liberando il thread dell'interfaccia utente per altre attività critiche dell'app. Per farlo, puoi utilizzare
shouldRunUiThreadStartUpTasks(false).
In un secondo momento del ciclo di vita dell'app, puoi chiamare di nuovo startUpWebView con
shouldRunUiThreadStartUpTasks(true) per completare le attività di avvio rimanenti sul
thread dell'interfaccia utente. Se attendere il callback a questo punto dipende dal fatto che l'utilizzo di WebView si trovi o meno nel percorso critico.
Esempio di implementazione
L'API utilizza il callback androidx.webkit.WebViewOutcomeReceiver, che ti consente di monitorare le inizializzazioni riuscite o gestire gli errori di diagnostica.
È sicuro chiamare startUpWebView più volte da diverse parti dell'app. Ti consigliamo di evitare di implementare un ciclo di nuovi tentativi ingenuo.
Il seguente esempio di codice mostra come utilizzare l'API
WebViewCompat.startUpWebView per l'inizializzazione asincrona.
Kotlin
import android.content.Context
import android.util.Log
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewOutcomeReceiver
import androidx.webkit.WebViewStartUpConfig
import androidx.webkit.WebViewStartUpResult
import androidx.webkit.WebViewStartupException
import java.util.concurrent.Executors
fun initializeWebView(context: Context) {
// 1. Create a startup configuration specifying the background thread
// that WebView will use to run its initialization tasks.
val startUpConfig = WebViewStartUpConfig.Builder(
Executors.newSingleThreadExecutor()
).build()
// 2. Trigger WebView startup asynchronously
WebViewCompat.startUpWebView(
context,
startUpConfig,
object : WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException> {
override fun onResult(result: WebViewStartUpResult) {
// Success: The WebView has finished its background initialization.
// This callback is guaranteed to be invoked on the UI thread.
setupWebView()
}
override fun onError(error: WebViewStartupException) {
// Failure: The initialization encountered a startup exception.
Log.e("WebViewStartup", "Failed to initialize WebView", error)
}
}
)
}
Java
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewOutcomeReceiver;
import androidx.webkit.WebViewStartUpConfig;
import androidx.webkit.WebViewStartUpResult;
import androidx.webkit.WebViewStartupException;
import java.util.concurrent.Executors;
public void initializeWebView(Context context) {
// 1. Create the startup configuration specifying the background thread pool
// to handle internal non-UI initialization processes.
WebViewStartUpConfig startUpConfig = new WebViewStartUpConfig.Builder(
Executors.newSingleThreadExecutor()
).build();
// 2. Trigger WebView startup asynchronously
WebViewCompat.startUpWebView(
context,
startUpConfig,
new WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException>() {
@Override
public void onResult(@NonNull WebViewStartUpResult result) {
// Success: The WebView has finished its background initialization.
// This callback is invoked directly on the UI thread.
setupWebView();
}
@Override
public void onError(@NonNull WebViewStartupException error) {
// Failure: Handled using the concrete WebViewStartupException
Log.e("WebViewStartup", "Failed to initialize WebView", error);
}
}
);
}
Eseguire il debug dei problemi di avvio asincrono
Se startUpWebView non produce i vantaggi in termini di rendimento previsti, spesso
è perché WebView viene inizializzato implicitamente altrove nell'app prima
dell'esecuzione della chiamata. Le cause potrebbero essere le seguenti:
Librerie di terze parti o SDK inizializzati all'inizio del ciclo di vita dell'app.
ContentProvidersinserito nel tuo APK che attiva le API WebView durante l'avvio dell'app.Gonfiature del layout o chiamate programmatiche (come il recupero di stringhe user agent) che si verificano inaspettatamente in anticipo.
Per aiutarti a diagnosticare dove e perché si verificano queste inizializzazioni impreviste, l'oggetto WebViewStartUpResult fornisce funzionalità di controllo integrate:
getUiThreadBlockingStartUpLocations(): restituisce un elenco di oggettiStartUpLocationche rappresentano le posizioni in cui le attività di avvio di WebView hanno bloccato il thread dell'interfaccia utente principale.getNonUiThreadBlockingStartUpLocations(): restituisce siti di chiamate specifici in cui l'esecuzione di attività di avvio ha bloccato i thread in background.
Ogni StartUpLocation contiene un'analisi dello stack che puoi registrare o ispezionare per
trovare la classe e il metodo esatti che hanno attivato l'inizializzazione.
Esempio di implementazione
Puoi esaminare queste posizioni all'interno del callback onResult per controllare il percorso di avvio:
override fun onResult(result: WebViewStartUpResult) {
// Check if WebView startup was blocked on the UI thread prior to or during initialization
val uiBlockingLocations = result.getUiThreadBlockingStartUpLocations()
if (!uiBlockingLocations.isNullOrEmpty()) {
for (location in uiBlockingLocations) {
// Log the stack trace of the call site that triggered the UI-blocking startup
Log.w("WebViewDebug", "WebView startup blocked the UI thread here:", location.getStack())
}
} else {
Log.i("WebViewDebug", "Excellent! No UI-blocking WebView startup detected.")
}
// Check where background initialization tasks were executed
val backgroundLocations = result.getNonUiThreadBlockingStartUpLocations()
backgroundLocations?.forEach { location ->
Log.d("WebViewDebug", "WebView background startup occurred at: ${location.getStack()}")
}
setupWebView()
}
Come utilizzare questi dati durante un audit
Quando esegui l'audit dell'avvio di WebView della tua app, utilizza le seguenti strategie per analizzare i dati di diagnostica e risolvere i colli di bottiglia delle prestazioni:
Cerca stack trace imprevisti:se
getUiThreadBlockingStartUpLocations()non è vuoto, esamina gli stack trace stampati. Se visualizzi classi appartenenti a SDK di terze parti o componenti imprevisti, hai trovato un collo di bottiglia di inizializzazione implicita.Verifica l'ordine di chiamata:se gli output dei log mostrano che si è verificata un'inizializzazione implicita prima della chiamata
startUpWebViewmanuale, devi spostare l'inizializzazionestartUpWebViewin un punto precedente dell'app o configurare l'SDK incriminato in modo da ritardare le attività dipendenti da WebView.
Migrazione dalle soluzioni alternative precedenti
In passato, potresti aver utilizzato soluzioni alternative esplicite per forzare l'inizializzazione di WebView su un thread in background, ad esempio recuperando la stringa user agent.
Queste soluzioni alternative sono considerate pratiche non supportate e il loro comportamento sottostante può cambiare nelle release future. Se la tua app si basa su soluzioni alternative esplicite e non documentate per attivare o gestire l'avvio di WebView, ti consigliamo di utilizzare l'API startUpWebView. L'API startUpWebView funziona su tutte le versioni di Android e WebView supportate dalla libreria Jetpack Webkit.
L'utilizzo dell'implementazione di Jetpack Webkit contribuisce a garantire un comportamento coerente in tutto l'ecosistema Android. Un vantaggio fondamentale di questa API è la sua resilienza: sui dispositivi legacy in cui non sono disponibili ottimizzazioni più recenti, l'API mantiene la parità di prestazioni con le soluzioni alternative manuali. In questo modo, puoi adottare i vantaggi moderni delle startup sui dispositivi più recenti senza incorrere in una penalità di rendimento su quelli meno recenti.
Se riscontri problemi o hai feedback sull'API startUpWebView, segnala un
bug nel public issue tracker.