Quando la tua app utilizza una WebView per la prima volta, il sistema esegue attività di avvio specifiche.
Questa procedura di avvio è pesante. Per impostazione predefinita, si verifica implicitamente nel thread dell'UI la prima volta che l'applicazione chiama molte API all'interno dei pacchetti android.webkit o androidx.webkit o espande un layout che contiene un tag WebView.
Perché è importante
Poiché questo avvio implicito avviene interamente nel thread principale, impedisce alla tua app di elaborare l'input dell'utente e aumenta drasticamente il rischio di errori di tipo "L'applicazione non risponde" (ANR). Per ulteriori informazioni su come Android gestisce il modello di esecuzione a thread singolo, consulta la panoramica su processi e thread overview.
Trigger per l'avvio implicito
L'avvio implicito può essere attivato nei seguenti modi:
- A livello di programmazione: chiamando API come
WebSettings.getUserAgentString(). - Utilizzando i layout: chiamando
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 di visualizzazione iniziale. Se l'inizializzazione implicita non è
ottimale per la tua app, utilizza startUpWebView invece.
Questa pagina spiega come ottimizzare il rendimento di avvio di WebView utilizzando l'API startUpWebView.
Assumere il controllo dell'avvio di WebView
Per migliorare il rendimento e ridurre al minimo gli errori ANR, utilizza l'API startUpWebView disponibile nella libreria Jetpack Webkit. Questa API ti offre il controllo esplicito sul momento in cui viene avviata WebView. Trasferisce una quantità significativa del carico di lavoro di avvio a un thread in background e consente di eseguire in blocchi qualsiasi lavoro che deve essere eseguito nel thread dell'UI, anziché in un unico blocco monolitico di grandi dimensioni. In questo modo, il thread dell'UI è libero di 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 la tua app deve effettivamente visualizzare la WebView.
Quando WebView non è nel percorso critico
Se la tua app non deve caricare immediatamente una WebView, puoi nascondere completamente il costo di inizializzazione. Chiama startUpWebView all'inizio del ciclo di vita dell'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 venga completata prima di toccare altri componenti WebView, il sistema blocca il thread dell'UI durante l'attesa del completamento dell'inizializzazione. La tua app potrebbe ottenere un certo miglioramento del rendimento dal lavoro in background già completato, ma non il massimo.
Quando WebView è nel percorso critico
Se il percorso utente principale della tua app richiede immediatamente una 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 attendere l'attivazione del callback. Utilizza invece le API WebView direttamente quando sono necessarie.
Per ottenere il massimo vantaggio dall'avvio asincrono, è fondamentale rimandare l'istanza di una WebView o la chiamata alle API WebView finché non rimangono altre operazioni del thread dell'UI nel percorso critico da eseguire (ad esempio l'espansione delle gerarchie di layout, l'inizializzazione di altri SDK o il disegno del frame iniziale).
Se chiami startUpWebView e subito dopo richiami le API WebView nel thread principale, il thread dell'UI si blocca in attesa che l'inizializzazione venga completata. In questo scenario, non si ottiene alcun miglioramento del rendimento.
Se l'utilizzo di WebView può diventare nel percorso critico, ma non vuoi avviare completamente WebView, puoi scegliere di eseguire selettivamente le attività di avvio di WebView che possono essere eseguite in un thread in background, liberando il thread dell'UI per altre attività critiche dell'app. A questo scopo, puoi utilizzare shouldRunUiThreadStartUpTasks(false).
Più avanti nel ciclo di vita dell'app, puoi chiamare di nuovo startUpWebView con shouldRunUiThreadStartUpTasks(true) per completare le attività di avvio rimanenti nel thread dell'UI. Se attendere o meno il callback a questo punto dipende dal fatto che l'utilizzo di WebView sia 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 ripetizione 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 miglioramenti del rendimento previsti, spesso è perché WebView viene inizializzata implicitamente altrove nell'app prima dell'esecuzione della chiamata. Questo potrebbe essere dovuto ai seguenti motivi:
Librerie di terze parti o SDK inizializzati all'inizio del ciclo di vita dell'app.
ContentProvidersinseriti nel tuo APK che attivano le API WebView durante l'avvio dell'app.Espansioni di layout o chiamate programmatiche (ad esempio il recupero delle stringhe dello user agent) che si verificano inaspettatamente all'inizio.
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'UI principale.getNonUiThreadBlockingStartUpLocations(): restituisce siti di chiamata specifici in cui l'esecuzione delle attività di avvio ha bloccato i thread in background.
Ogni StartUpLocation contiene una traccia dello stack che puoi registrare o ispezionare per trovare la classe e il metodo esatti che hanno attivato l'inizializzazione.
Esempio di implementazione
Puoi ispezionare 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 controllo
Quando controlli l'avvio di WebView della tua app, utilizza le seguenti strategie per analizzare i dati di diagnostica e risolvere i colli di bottiglia del rendimento:
Cerca tracce dello stack impreviste:se
getUiThreadBlockingStartUpLocations()non è vuoto, esamina le tracce dello stack stampate. Se vedi classi appartenenti a SDK di terze parti o componenti imprevisti, hai trovato un collo di bottiglia di inizializzazione implicita.Verifica l'ordine delle chiamate:se gli output dei log mostrano che si è verificata un'inizializzazione implicita prima della chiamata manuale
startUpWebView, devi spostare l'inizializzazionestartUpWebViewall'inizio dell'app o configurare l'SDK che causa il problema in modo da ritardare le attività dipendenti da WebView.
Eseguire la migrazione dalle soluzioni alternative precedenti
In passato, potresti aver utilizzato soluzioni alternative esplicite per forzare l'inizializzazione di WebView in un thread in background, ad esempio recuperando la stringa dello 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 invece l'API startUpWebView. L'API startUpWebView funziona su tutte le versioni di Android e WebView supportate dalla libreria Jetpack Webkit.
Garantire la resilienza e la stabilità dell'app
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 meno recenti in cui le ottimizzazioni più recenti non sono disponibili, l'API mantiene la parità di rendimento con le soluzioni alternative manuali. In questo modo, puoi adottare i vantaggi dell'avvio moderno sui dispositivi più recenti senza incorrere in una penalità di rendimento su quelli meno recenti.
Sebbene l'ottimizzazione dell'avvio di WebView riduca il rischio di errori ANR durante l'avvio dell'app, devi anche proteggere l'app da arresti anomali del renderer in fase di runtime e dal recupero della memoria di sistema. Per mantenere la stabilità completa dell'app una volta che il tuo WebView è in esecuzione, consulta Gestire la terminazione di WebView.
Se riscontri problemi o hai feedback sull'API startUpWebView, segnala un
bug nel tracker di problemi pubblico.