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 przy pierwszym wywołaniu przez aplikację wielu interfejsów API w pakietach android.webkit lub androidx.webkit albo przy rozwijaniu układu zawierającego 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
Uruchomienie niejawne może być wywołane w ten sposób:
- Programowo: wywoływanie interfejsów API, np.
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 dane biznesowe, takie jak czas uruchamiania aplikacji i czas do pierwszego wyświetlenia. Jeśli inicjowanie niejawne nie jest optymalne w przypadku Twojej aplikacji, użyj zamiast niego 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ć liczbę błędów ANR, użyj startUpWebViewinterfejsu API dostępnego w bibliotece Jetpack Webkit. Ten interfejs API umożliwia wyraźne określenie, kiedy ma się uruchomić WebView. Przenosi znaczną część obciążenia związanego z uruchamianiem do wątku w tle i umożliwia wykonywanie w częściach wszelkich działań, które muszą być realizowane w wątku interfejsu, 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ć element 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.
Przed wywołaniem innych interfejsów WebView API najlepiej poczekać na wywołanie zwrotne. Jeśli wywołasz startUpWebView, ale nie poczekasz na zakończenie tego procesu przed dotknięciem innych komponentów WebView, system zablokuje wątek interfejsu, 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 elementu WebView lub wywoływanie interfejsów API WebView do momentu, gdy nie będzie już żadnych innych operacji na wątku interfejsu, które są krytyczne (np. rozwijanie hierarchii układu, inicjowanie innych pakietów SDK lub rysowanie początkowej ramki).
Jeśli wywołasz startUpWebView i natychmiast potem wywołasz interfejsy WebView API w wątku głównym, wątek interfejsu użytkownika zostanie zablokowany, 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 uruchomić wybrane zadania uruchamiania komponentu WebView, które mogą być wykonywane w wątku w tle, zwalniając wątek interfejsu na potrzeby innych krytycznych zadań aplikacji. Możesz do tego użyć shouldRunUiThreadStartUpTasks(false).
Na późniejszym etapie działania aplikacji możesz ponownie wywołać funkcję startUpWebView z parametrem shouldRunUiThreadStartUpTasks(true), aby dokończyć pozostałe zadania uruchamiania w wątku interfejsu. 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 inicjacji lub obsługę błędów diagnostycznych.
Możesz bezpiecznie wywoływać funkcję 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 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 w zakresie skutecznoś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.Rozszerzanie układu 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 witryny wywołań, w których uruchamianie zadań startowych blokowało wątki w tle.
Każdy element StartUpLocation zawiera ślad stosu, który możesz zarejestrować lub sprawdzić, aby znaleźć dokładną klasę i metodę, które wywołały inicjalizację.
Przykład implementacji
Te lokalizacje możesz sprawdzić 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 używaj tych strategii, aby analizować dane diagnostyczne i usuwać wąskie gardła wydajności:
Szukaj nieoczekiwanych śladów stosu: jeśli
getUiThreadBlockingStartUpLocations()nie jest pusty, 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 inicjowaniu niejawnym.Sprawdź kolejność wywołań: jeśli dane wyjściowe dziennika 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ć jawne 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 jakichkolwiek jawnych, nieudokumentowanych obejść, aby wywołać lub zarządzać uruchamianiem komponentu WebView, zalecamy używanie startUpWebView interfejsu 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 wystąpienia 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.
Jeśli napotkasz problemy lub masz uwagi dotyczące startUpWebViewinterfejsu API, zgłoś błąd w publicznym narzędziu do rejestrowania problemów.