Gdy aplikacja po raz pierwszy używa komponentu WebView, system wykonuje określone zadania uruchamiania.
Proces uruchamiania jest złożony. Domyślnie dzieje się to niejawnie w wątku interfejsu użytkownika, gdy aplikacja po raz pierwszy wywołuje wiele interfejsów API w pakietach android.webkit lub androidx.webkit albo gdy wczytuje układ zawierający tag WebView.
Dlaczego to jest ważne
Ponieważ to domyślne uruchomienie odbywa się w całości w wątku głównym, blokuje ono przetwarzanie danych wejściowych użytkownika przez aplikację i drastycznie zwiększa ryzyko wystąpienia błędów typu „Aplikacja nie odpowiada” (ANR). Więcej informacji o tym, jak Android obsługuje model wykonywania jednowątkowego, znajdziesz w omówieniu procesów i wątków.
Aktywatory niejawnego uruchamiania
Niejawne uruchomienie może być wywołane w ten sposób:
- Programowo: wywoływanie interfejsów API, takich jak
WebSettings.getUserAgentString(). - Korzystanie z układów: wywołanie funkcji
setContentView()lublayoutInflater.inflate()w zasobie XML, który zawiera element<WebView>.
Niejawne uruchamianie może też negatywnie wpływać na wskaźniki biznesowe, takie jak czas uruchamiania aplikacji i czas do pierwszego wyświetlenia. Jeśli niejawna inicjalizacja nie jest optymalna w przypadku Twojej aplikacji, użyj zamiast niej startUpWebView.
Na tej stronie dowiesz się, jak zoptymalizować wydajność uruchamiania komponentu WebView za pomocą interfejsu API startUpWebView.
Przejmowanie kontroli nad uruchamianiem WebView
Aby zwiększyć wydajność i zminimalizować błędy ANR, użyj startUpWebView interfejsu API dostępnego w bibliotece Jetpack Webkit. Ten interfejs API umożliwia wyraźne określenie, kiedy ma się uruchomić WebView. Przenosi znaczną część zbioru zadań uruchamiania do wątku w tle i umożliwia wykonywanie w częściach wszelkich zadań, które muszą być realizowane w wątku UI, zamiast w jednym dużym bloku. Dzięki temu wątek interfejsu użytkownika może równolegle obsługiwać inne ważne zadania aplikacji, co zmniejsza ryzyko zablokowania wrażeń użytkownika.
Interfejs API używa wywołania zwrotnego androidx.webkit.WebViewOutcomeReceiver, które umożliwia śledzenie udanych inicjacji.
Aby korzystać z 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 API startUpWebView
Sposób optymalizacji procesu uruchamiania zależy od tego, kiedy aplikacja musi wyświetlić WebView.
Gdy komponent WebView nie znajduje się na ścieżce krytycznej
Jeśli aplikacja nie musi od razu wczytywać komponentu WebView, możesz całkowicie ukryć koszt inicjowania. Wywołaj funkcję startUpWebView na wczesnym etapie cyklu życia aplikacji i poczekaj na wywołanie zwrotne informujące o sukcesie.
Najlepiej poczekać na wywołanie zwrotne przed wywołaniem innych interfejsów WebView API. Jeśli wywołasz startUpWebView, ale nie poczekasz na zakończenie tego procesu przed dotknięciem innych komponentów WebView, system zablokuje wątek UI, czekając na zakończenie inicjowania. Aplikacja może zyskać pewne korzyści z wykonanej już pracy w tle, ale nie maksymalne.
Gdy komponent WebView znajduje się na ścieżce krytycznej
Jeśli kluczowa ścieżka użytkownika w Twojej aplikacji wymaga natychmiastowego użycia komponentu WebView, prawdopodobnie nie możesz czekać na zakończenie jego uruchamiania. W takim przypadku nadal musisz wywołać funkcję startUpWebView jak najwcześniej w cyklu życia aplikacji (np. w funkcji Application.onCreate), ale nie czekaj na wywołanie zwrotne. Zamiast tego w razie potrzeby używaj bezpośrednio interfejsów WebView API.
Aby w pełni wykorzystać zalety asynchronicznego uruchamiania, odłóż tworzenie instancji komponentu WebView lub wywoływanie interfejsów API WebView do momentu, gdy nie będzie już żadnych innych operacji na wątku UI na ścieżce krytycznej (takich jak rozszerzanie hierarchii układu, inicjowanie innych pakietów SDK czy rysowanie początkowej ramki).
Jeśli wywołasz startUpWebView i natychmiast po tym wywołasz interfejsy WebView API w wątku głównym, wątek UI zablokuje się, czekając na zakończenie inicjowania. W tym scenariuszu nie ma korzyści związanych ze skutecznością.
Jeśli użycie komponentu WebView może stać się krytyczną ścieżką, ale nie chcesz go w pełni uruchamiać, możesz selektywnie uruchamiać zadania uruchamiania komponentu WebView, które mogą być wykonywane w wątku w tle, zwalniając 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ć funkcję startUpWebView z parametrem 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 jest na ścieżce krytycznej.
Przykład implementacji
Interfejs API używa wywołania zwrotnego androidx.webkit.WebViewOutcomeReceiver, które umożliwia śledzenie udanych inicjalizacji lub obsługę błędów diagnostycznych.
Funkcję startUpWebView można wywoływać wielokrotnie z różnych części aplikacji. Zalecamy unikanie implementowania prostej pętli ponawiania.
Poniższy przykładowy kod pokazuje, jak używać interfejsu WebViewCompat.startUpWebView API do asynchronicznej inicjacji.
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 funkcja startUpWebView nie przynosi oczekiwanych korzyści, często dzieje się tak, ponieważ element WebView jest inicjowany niejawnie w innej części aplikacji przed wykonaniem wywołania. Może to być spowodowane tymi przyczynami:
Biblioteki lub pakiety SDK innych firm zainicjowane na wczesnym etapie cyklu życia aplikacji.
ContentProviderswstrzykiwane do pliku APK, które wywołują interfejsy WebView API podczas uruchamiania aplikacji.Rozszerzenia układu lub wywołania programowe (np. pobieranie ciągów znaków agenta użytkownika), które występują nieoczekiwanie wcześnie.
Aby pomóc Ci zdiagnozować, gdzie i dlaczego dochodzi do tych nieoczekiwanych inicjacji, obiekt WebViewStartUpResult udostępnia wbudowane funkcje audytu:
getUiThreadBlockingStartUpLocations(): zwraca listę obiektówStartUpLocationreprezentujących lokalizacje, w których zadania uruchamiania WebView blokowały główny wątek interfejsu użytkownika.getNonUiThreadBlockingStartUpLocations(): zwraca konkretne witryny wywołań, w których uruchamianie zadań startowych blokowało wątki w tle.
Każdy element 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 implementacji
Możesz sprawdzić te lokalizacje w wywołaniu zwrotnym onResult, aby przeanalizować ścieżkę 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 wykorzystać te dane podczas audytu
Podczas sprawdzania uruchamiania widoku WebView w aplikacji stosuj te strategie, aby analizować dane diagnostyczne i usuwać 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 występuje wąskie gardło w niejawnym procesie inicjowania.Sprawdź kolejność wywołań: jeśli dane wyjściowe logu wskazują, że przed ręcznym wywołaniem funkcji
startUpWebViewnastąpiła niejawna inicjalizacja, przenieś inicjalizację funkcjistartUpWebViewna wcześniejszy etap w aplikacji lub skonfiguruj problematyczny pakiet SDK tak, aby opóźniał zadania zależne od WebView.
Migracja z poprzednich obejść
W przeszłości mogłeś(-aś) stosować wyraźne obejścia, aby wymusić inicjowanie widoku internetowego 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ć lub zarządzać uruchamianiem komponentu WebView, zalecamy używanie startUpWebView API. Interfejs startUpWebView API działa we wszystkich wersjach Androida i WebView obsługiwanych przez bibliotekę Jetpack Webkit.
Zapewnianie odporności i stabilności aplikacji
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 wydajność porównywalną z ręcznymi obejściami. Dzięki temu możesz korzystać z nowoczesnych funkcji uruchamiania na nowszych urządzeniach bez obniżania wydajności starszych urządzeń.
Optymalizacja uruchamiania WebView zmniejsza ryzyko błędów ANR podczas uruchamiania aplikacji, ale musisz też chronić aplikację przed awariami renderowania w czasie działania i odzyskiwaniem pamięci systemowej. Aby zachować stabilność aplikacji po uruchomieniu komponentu WebView, zapoznaj się z artykułem Obsługa zakończenia działania komponentu WebView. Dodatkowo zachowaj stan użytkownika po śmierci procesu w tle, korzystając z WebView.saveState() i stosując bezpieczne dla pamięci praktyki opisane w artykule Efektywne zarządzanie stanem komponentu WebView.
Jeśli napotkasz problemy lub masz uwagi dotyczące startUpWebViewinterfejsu API, zgłoś błąd w publicznym narzędziu do śledzenia błędów.