Gdy aplikacja po raz pierwszy używa WebView, system wykonuje określone zadania uruchamiania.
Ten proces uruchamiania jest złożony. Domyślnie odbywa się on niejawnie w wątku interfejsu użytkownika, gdy aplikacja po raz pierwszy wywoła wiele interfejsów API w pakietach android.webkit lub androidx.webkit albo rozwinie układ zawierający tag WebView.
Dlaczego to jest ważne
Ponieważ to niejawne uruchamianie odbywa się w całości w wątku głównym, blokuje ono aplikację przed przetwarzaniem danych wejściowych użytkownika i znacznie zwiększa ryzyko wystąpienia błędów typu „Aplikacja nie odpowiada” (ANR). Więcej informacji o tym, jak Android obsługuje model wykonywania w jednym wątku, znajdziesz w artykule Omówienie procesów i wątków overview.
Przyczyny niejawnego uruchamiania
Niejawne uruchamianie może być wywoływane w te sposoby:
- Programowo: wywoływanie interfejsów API, takich jak
WebSettings.getUserAgentString(). - Za pomocą układów: wywoływanie
setContentView()lublayoutInflater.inflate()w zasobie XML, który zawiera<WebView>.
Niejawne uruchamianie może też negatywnie wpływać na dane biznesowe, takie jak czas uruchamiania aplikacji i czas do pierwszego wyświetlenia. Jeśli niejawna inicjalizacja nie jest
optymalna dla Twojej aplikacji, użyj startUpWebView zamiast niej.
Na tej stronie dowiesz się, jak zoptymalizować wydajność uruchamiania WebView za pomocą interfejsu startUpWebView API.
Przejmij kontrolę nad uruchamianiem WebView
Aby zwiększyć wydajność i zminimalizować liczbę błędów ANR, użyj interfejsu startUpWebView API dostępnego w bibliotece Jetpack Webkit. Ten interfejs API daje Ci pełną kontrolę nad tym, kiedy uruchamia się WebView. Przenosi on znaczną część obciążenia związanego z uruchamianiem do wątku w tle i umożliwia wykonywanie w wątku UI zadań w częściach, a nie w jednym dużym bloku. Dzięki temu wątek interfejsu użytkownika może równolegle obsługiwać inne krytyczne zadania aplikacji, co zmniejsza ryzyko zablokowania interfejsu.
Interfejs API używa wywołania zwrotnego androidx.webkit.WebViewOutcomeReceiver, co pozwala śledzić udane inicjalizacje.
Aby używać tego interfejsu API, dodaj bibliotekę Jetpack Webkit do pliku build.gradle.
Upewnij się, że używasz wersji 1.16.0 lub nowszej:
dependencies {
implementation("androidx.webkit:webkit:1.16.0")
}
Korzystanie z interfejsu startUpWebView API
Sposób optymalizacji procesu uruchamiania zależy od tego, kiedy aplikacja musi wyświetlić WebView.
Gdy WebView nie znajduje się na ścieżce krytycznej
Jeśli aplikacja nie musi od razu wczytywać WebView, możesz całkowicie ukryć koszt inicjalizacji. Wywołaj startUpWebView na wczesnym etapie cyklu życia aplikacji i poczekaj na wywołanie zwrotne.
Najlepiej poczekać na wywołanie zwrotne przed wywołaniem innych interfejsów WebView API. Jeśli wywołasz startUpWebView, ale nie poczekasz na jego zakończenie przed użyciem innych komponentów WebView, system zablokuje wątek UI, czekając na zakończenie inicjalizacji. Aplikacja może uzyskać pewne korzyści z pracy w tle, ale nie maksymalne.
Gdy WebView znajduje się na ścieżce krytycznej
Jeśli podstawowa ścieżka użytkownika w aplikacji wymaga natychmiastowego użycia WebView, prawdopodobnie nie możesz sobie pozwolić na czekanie na zakończenie uruchamiania WebView. W takim przypadku nadal należy wywołać startUpWebView jak najwcześniej w cyklu życia aplikacji
(np. w Application.onCreate), ale nie należy czekać na wywołanie zwrotne
. Zamiast tego używaj interfejsów WebView API bezpośrednio, gdy są potrzebne.
Aby uzyskać maksymalne korzyści z asynchronicznego uruchamiania, odłóż tworzenie instancji komponentu WebView lub wywoływanie interfejsów WebView API do momentu, gdy nie będzie już żadnych innych operacji w wątku UI na ścieżce krytycznej (takich jak rozszerzanie hierarchii układów, inicjowanie innych pakietów SDK czy rysowanie pierwszej klatki).
Jeśli wywołasz startUpWebView i natychmiast potem wywołasz interfejsy WebView API w wątku głównym, wątek UI zostanie zablokowany, czekając na zakończenie inicjalizacji. W takim przypadku nie ma żadnych korzyści z wydajności.
Jeśli użycie WebView może znaleźć się na ścieżce krytycznej, ale nie chcesz uruchamiać WebView w całości, możesz selektywnie uruchamiać zadania uruchamiania WebView, które mogą być wykonywane w wątku w tle, co zwalnia wątek UI na potrzeby innych krytycznych zadań aplikacji. W tym celu możesz użyć shouldRunUiThreadStartUpTasks(false).
Na późniejszym etapie cyklu życia aplikacji możesz ponownie wywołać startUpWebView z shouldRunUiThreadStartUpTasks(true), aby dokończyć pozostałe zadania uruchamiania w wątku UI. To, czy w tym momencie poczekasz na wywołanie zwrotne, zależy od tego, czy użycie WebView znajduje się na ścieżce krytycznej.
Przykład wdrożenia
Interfejs API używa wywołania zwrotnego androidx.webkit.WebViewOutcomeReceiver, co pozwala śledzić udane inicjalizacje lub obsługiwać błędy diagnostyczne.
Możesz bezpiecznie wywoływać startUpWebView wiele razy z różnych części aplikacji. Zalecamy unikanie implementowania prostego cyklu ponawiania.
Poniższy przykładowy kod pokazuje, jak używać interfejsu WebViewCompat.startUpWebView API do asynchronicznej inicjalizacji.
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);
}
}
);
}
Debugowanie problemów z asynchronicznym uruchamianiem
Jeśli startUpWebView nie przynosi oczekiwanych korzyści z wydajności, często dzieje się tak, ponieważ WebView jest inicjowany niejawnie w innym miejscu aplikacji przed wykonaniem wywołania. Może to być spowodowane tymi przyczynami:
Biblioteki innych firm lub pakiety SDK inicjowane na wczesnym etapie cyklu życia aplikacji.
ContentProviderswstrzykiwane do pliku APK, które wywołują interfejsy WebView API podczas uruchamiania aplikacji.Rozwijanie układów lub wywołania programowe (np. pobieranie ciągów znaków agenta użytkownika), które występują niespodziewanie wcześnie.
Aby pomóc Ci zdiagnozować, gdzie i dlaczego dochodzi do tych nieoczekiwanych inicjalizacji, obiekt WebViewStartUpResult udostępnia wbudowane funkcje audytu:
getUiThreadBlockingStartUpLocations(): zwraca listę obiektówStartUpLocationreprezentujących miejsca, w których zadania uruchamiania WebView blokowały główny wątek interfejsu użytkownika.getNonUiThreadBlockingStartUpLocations(): zwraca konkretne miejsca wywołań, w których uruchamianie zadań blokowało wątki w tle.
Każdy StartUpLocation zawiera zrzut stosu, który możesz zarejestrować lub sprawdzić, aby znaleźć dokładną klasę i metodę, które wywołały inicjalizację.
Przykład wdrożenia
Możesz sprawdzić te lokalizacje w wywołaniu zwrotnym onResult, aby przeprowadzić audyt ścieżki uruchamiania:
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()
}
Jak korzystać z tych danych podczas audytu
Podczas audytu uruchamiania WebView w aplikacji użyj tych strategii, aby analizować dane diagnostyczne i eliminować wąskie gardła wydajności:
Szukaj nieoczekiwanych śladów stosu: jeśli
getUiThreadBlockingStartUpLocations()nie jest puste, sprawdź wydrukowane ślady stosu. Jeśli widzisz klasy należące do pakietów SDK innych firm lub nieoczekiwane komponenty, oznacza to, że znalazłeś wąskie gardło niejawnej inicjalizacji.Sprawdź kolejność wywołań: jeśli dane wyjściowe logu wskazują, że niejawna inicjalizacja nastąpiła przed ręcznym wywołaniem
startUpWebView, przenieś inicjalizacjęstartUpWebViewna wcześniejszy etap aplikacji lub skonfiguruj pakiet SDK, który powoduje problem, tak aby opóźnić jego zadania zależne od WebView.
Migracja z poprzednich obejść
W przeszłości mogłeś używać jawnych obejść, aby wymusić inicjalizację WebView w wątku w tle, np. pobierając ciąg znaków agenta użytkownika.
Te obejścia są uważane za nieobsługiwane praktyki, a ich podstawowe działanie może się zmienić w przyszłych wersjach. Jeśli Twoja aplikacja korzysta z jawnych, nieudokumentowanych obejść, aby wywoływać uruchamianie WebView lub nim zarządzać, zalecamy używanie zamiast nich interfejsu startUpWebView API. Interfejs startUpWebView API działa we wszystkich wersjach Androida i WebView obsługiwanych przez bibliotekę Jetpack Webkit.
Korzystanie z implementacji Jetpack Webkit pomaga zapewnić spójne działanie w całym ekosystemie Androida. Kluczową zaletą tego interfejsu API jest jego odporność: na starszych urządzeniach, na których nie są dostępne nowsze optymalizacje, interfejs API zachowuje równą wydajność w porównaniu z ręcznymi obejściami. Dzięki temu możesz korzystać z nowoczesnych funkcji uruchamiania na nowszych urządzeniach bez utraty wydajności na starszych.
Jeśli napotkasz problemy lub masz opinie na temat interfejsu startUpWebView API, zgłoś
błąd w publicznym narzędziu do śledzenia problemów.