Wenn Ihre App zum ersten Mal eine WebView verwendet, führt das System bestimmte Startaufgaben aus.
Dieser Startvorgang ist aufwendig. Standardmäßig geschieht dies implizit im UI-Thread, wenn die Anwendung zum ersten Mal viele APIs in den Paketen android.webkit oder androidx.webkit aufruft oder ein Layout rendert, das ein WebView-Tag enthält.
Warum das wichtig ist
Da dieser implizite Start vollständig im Hauptthread erfolgt, wird Ihre App daran gehindert, Nutzereingaben zu verarbeiten. Außerdem erhöht sich das Risiko von ANR-Fehlern („App antwortet nicht“) drastisch. Weitere Informationen dazu, wie Android das Single-Thread-Ausführungsmodell verarbeitet, finden Sie unter Prozesse und Threads – Übersicht.
Trigger für den impliziten Start
Ein impliziter Start kann auf folgende Weise ausgelöst werden:
- Programmatisch: Durch Aufrufen von APIs wie
WebSettings.getUserAgentString(). - Layouts verwenden:
setContentView()oderlayoutInflater.inflate()für eine XML-Ressource aufrufen, die ein<WebView>enthält.
Der implizite Start kann sich auch negativ auf Ihre Geschäftsmesswerte auswirken, z. B. auf die App-Startzeit und die Zeit bis zur ersten Anzeige. Wenn die implizite Initialisierung für Ihre App nicht optimal ist, verwenden Sie stattdessen startUpWebView.
Auf dieser Seite wird beschrieben, wie Sie die Startleistung von WebView mithilfe der startUpWebView API optimieren können.
WebView-Start steuern
Um die Leistung zu verbessern und ANRs zu minimieren, verwenden Sie die startUpWebView API, die in der Jetpack Webkit-Bibliothek verfügbar ist. Mit dieser API können Sie explizit steuern, wann WebView gestartet wird. Dadurch wird ein erheblicher Teil der Startup-Arbeitslast auf einen Hintergrundthread verlagert und alle Arbeiten, die im UI-Thread ausgeführt werden müssen, können in Blöcken statt in einem großen monolithischen Block erledigt werden. So wird der UI-Thread für andere wichtige App-Aufgaben freigegeben, wodurch die Wahrscheinlichkeit verringert wird, dass die Nutzerfreundlichkeit beeinträchtigt wird.
Die API verwendet den androidx.webkit.WebViewOutcomeReceiver-Callback, damit Sie erfolgreiche Initialisierungen nachverfolgen können.
Wenn Sie diese API verwenden möchten, fügen Sie die Jetpack Webkit-Bibliothek zu Ihrer build.gradle-Datei hinzu.
Achten Sie darauf, dass Sie Version 1.16.0 oder höher verwenden:
dependencies {
implementation("androidx.webkit:webkit:1.16.0")
}
startUpWebView API verwenden
Wie Sie den Startvorgang optimieren, hängt davon ab, wann Ihre App die WebView tatsächlich anzeigen muss.
Wenn WebView nicht Teil des kritischen Pfads ist
Wenn Ihre App nicht sofort eine WebView laden muss, können Sie die Initialisierungskosten vollständig verbergen. Rufen Sie startUpWebView früh im Lebenszyklus Ihrer App auf und warten Sie, bis der Erfolgs-Callback ausgelöst wird.
Idealerweise sollten Sie auf den Callback warten, bevor Sie andere WebView-APIs aufrufen. Wenn Sie startUpWebView auslösen, aber nicht warten, bis der Vorgang abgeschlossen ist, bevor Sie andere WebView-Komponenten berühren, blockiert das System den UI-Thread, während es auf den Abschluss der Initialisierung wartet. Ihre App profitiert möglicherweise von den bereits ausgeführten Hintergrundaufgaben, aber nicht im vollen Umfang.
Wenn WebView auf dem kritischen Pfad liegt
Wenn für den Hauptnutzerablauf Ihrer App sofort eine WebView erforderlich ist, können Sie wahrscheinlich nicht warten, bis das Starten der WebView abgeschlossen ist. In diesem Fall sollten Sie startUpWebView so früh wie möglich im Lebenszyklus der App aufrufen (z. B. in Application.onCreate), aber nicht auf den Callback warten. Verwenden Sie stattdessen WebView APIs direkt, wenn sie erforderlich sind.
Um den maximalen Nutzen aus dem asynchronen Start zu ziehen, sollten Sie die Instanziierung eines WebView-Objekts oder den Aufruf von WebView-APIs unbedingt aufschieben, bis keine anderen kritischen UI-Thread-Operationen mehr ausgeführt werden müssen, z. B. das Aufblähen von Layouthierarchien, das Initialisieren anderer SDKs oder das Zeichnen des ersten Frames.
Wenn Sie startUpWebView aufrufen und danach sofort WebView-APIs im Hauptthread aufrufen, wird der UI-Thread blockiert, bis die Initialisierung abgeschlossen ist. In diesem Szenario gibt es keinen Leistungsvorteil.
Wenn die WebView-Nutzung zum kritischen Pfad werden kann, Sie aber nicht die gesamte WebView starten möchten, können Sie die WebView-Startaufgaben, die in einem Hintergrundthread ausgeführt werden können, selektiv ausführen. So wird der UI-Thread für andere app-kritische Aufgaben freigegeben. Dazu können Sie shouldRunUiThreadStartUpTasks(false) verwenden.
Später im Lebenszyklus Ihrer App können Sie startUpWebView noch einmal mit shouldRunUiThreadStartUpTasks(true) aufrufen, um die verbleibenden Startaufgaben im UI-Thread abzuschließen. Ob Sie an dieser Stelle auf den Callback warten, hängt davon ab, ob die WebView-Nutzung auf dem kritischen Pfad liegt.
Implementierungsbeispiel
Die API verwendet den androidx.webkit.WebViewOutcomeReceiver-Callback, sodass Sie erfolgreiche Initialisierungen nachverfolgen oder Diagnosefehler beheben können.
Es ist unbedenklich, startUpWebView mehrmals aus verschiedenen Teilen Ihrer App aufzurufen. Wir empfehlen, keine naiven Wiederholungsschleifen zu implementieren.
Das folgende Codebeispiel zeigt, wie Sie die WebViewCompat.startUpWebView API für die asynchrone Initialisierung verwenden.
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);
}
}
);
}
Probleme beim asynchronen Start beheben
Wenn startUpWebView nicht die erwarteten Leistungsvorteile bringt, liegt das oft daran, dass WebView an anderer Stelle in Ihrer App implizit initialisiert wird, bevor Ihr Aufruf ausgeführt wird. Das kann folgende Gründe haben:
Drittanbieterbibliotheken oder SDKs, die früh im App-Lebenszyklus initialisiert werden.
ContentProviders, die in Ihr APK eingefügt werden und beim Starten der App WebView APIs auslösen.Layout-Inflationen oder programmatische Aufrufe (z. B. zum Abrufen von User-Agent-Strings), die unerwartet früh erfolgen.
Um herauszufinden, wo und warum diese unerwarteten Initialisierungen auftreten, bietet das WebViewStartUpResult-Objekt integrierte Prüffunktionen:
getUiThreadBlockingStartUpLocations(): Gibt eine Liste vonStartUpLocation-Objekten zurück, die Orte darstellen, an denen WebView-Startaufgaben den Haupt-UI-Thread blockiert haben.getNonUiThreadBlockingStartUpLocations(): Gibt bestimmte Aufrufstellen zurück, an denen die Ausführung von Startaufgaben Hintergrundthreads blockiert hat.
Jeder StartUpLocation enthält einen Stacktrace, den Sie protokollieren oder prüfen können, um die genaue Klasse und Methode zu finden, die die Initialisierung ausgelöst hat.
Implementierungsbeispiel
Sie können diese Speicherorte in Ihrem onResult-Callback untersuchen, um Ihren Startpfad zu prüfen:
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()
}
So verwenden Sie diese Daten bei einer Prüfung
Verwenden Sie beim Prüfen des WebView-Starts Ihrer App die folgenden Strategien, um die Diagnosedaten zu analysieren und Leistungsengpässe zu beheben:
Nach unerwarteten Stacks suchen:Wenn
getUiThreadBlockingStartUpLocations()nicht leer ist, sehen Sie sich die ausgegebenen Stacks an. Wenn Sie Klassen sehen, die zu Drittanbieter-SDKs oder unerwarteten Komponenten gehören, haben Sie einen Engpass bei der impliziten Initialisierung gefunden.Anrufreihenfolge überprüfen:Wenn in Ihren Log-Ausgaben zu sehen ist, dass eine implizite Initialisierung vor Ihrem manuellen
startUpWebView-Aufruf erfolgt ist, sollten Sie diestartUpWebView-Initialisierung in Ihrer App an eine frühere Stelle verschieben oder das betreffende SDK so konfigurieren, dass seine WebView-abhängigen Aufgaben verzögert werden.
Von früheren Workarounds migrieren
In der Vergangenheit haben Sie möglicherweise explizite Workarounds verwendet, um die WebView-Initialisierung in einem Hintergrundthread zu erzwingen, z. B. durch Abrufen des User-Agent-Strings.
Diese Problemumgehungen gelten als nicht unterstützte Methoden und das zugrunde liegende Verhalten kann sich in zukünftigen Releases ändern. Wenn Ihre App auf explizite, nicht dokumentierte Workarounds angewiesen ist, um den WebView-Start auszulösen oder zu verwalten, empfehlen wir, stattdessen die startUpWebView API zu verwenden. Die startUpWebView API funktioniert auf allen Android- und WebView-Versionen, die von der Jetpack Webkit-Bibliothek unterstützt werden.
Die Verwendung der Jetpack Webkit-Implementierung trägt dazu bei, dass das Verhalten im gesamten Android-Ökosystem einheitlich ist. Ein wichtiger Vorteil dieser API ist ihre Robustheit: Auf älteren Geräten, auf denen neuere Optimierungen nicht verfügbar sind, bietet die API die gleiche Leistung wie manuelle Workarounds. So können Sie moderne Vorteile für Start-ups auf neueren Geräten nutzen, ohne dass die Leistung auf älteren Geräten beeinträchtigt wird.
Wenn Sie Probleme mit der startUpWebView API haben oder Feedback dazu geben möchten, reichen Sie einen Fehlerbericht im öffentlichen Issue Tracker ein.