Opóźnienie nawigacji to kluczowy wskaźnik wrażeń użytkownika. Aby pomóc deweloperom w zmniejszeniu tego opóźnienia, WebView udostępnia interfejsy API do ładowania spekulacyjnego, które umożliwiają aplikacji pobieranie lub renderowanie treści, zanim użytkownik wyraźnie przejdzie do nich.
Komponent WebView obsługuje 3 główne typy ładowania spekulacyjnego: Preconnect, Prefetch i Prerender.
Wdrożenie strategii ładowania spekulacyjnego umożliwia:
- Znaczne zmniejszenie opóźnienia wczytywania treści internetowych: przenieś czas rozpoczęcia połączenia sieciowego na wcześniejszy etap cyklu życia aplikacji.
- Wyższy odsetek udanych nawigacji: dzięki wstępnemu przygotowaniu sieci i pamięci podręcznej nawigacje są mniej podatne na awarie spowodowane przejściowymi problemami z siecią.
- Lepsze postrzeganie responsywności: w szczególności renderowanie wstępne umożliwia natychmiastowe przejścia, które sprawiają, że aplikacja działa znacznie szybciej.
Wybór strategii ładowania spekulacyjnego
Kluczowa różnica między tymi strategiami polega na ich zakresie: interfejs Preconnect API jest oparty na źródle, co oznacza, że wymaga tylko domeny docelowej. Interfejsy Prefetch i Prerender API są oparte na adresie URL, co oznacza, że wymagają dokładnej ścieżki strony internetowej.
Ponieważ Preconnect działa na poziomie źródła, można go zainicjować znacznie wcześniej w cyklu życia aplikacji, nawet zanim poznasz konkretną treść lub stronę, do której przejdzie użytkownik.
W tej tabeli porównujemy te 3 strategie, aby pomóc Ci wybrać odpowiednią dla Twojego przypadku użycia:
| Funkcja | Preconnect | Prefetch | Prerender |
|---|---|---|---|
| Główny cel | Przygotowanie połączenia | Buforowanie tylko kodu HTML (bez JavaScriptu i CSS) | Wstępne renderowanie całej strony |
| Zakres | Poziom profilu (wspólny dla wszystkich komponentów WebView) | Poziom profilu (wspólny dla wszystkich komponentów WebView) | Poziom WebView (powiązany z konkretnym komponentem WebView) |
| Jetpack WebKit API | androidx.webkit.Profile |
androidx.webkit.Profile |
androidx.webkit.WebViewCompat |
| Główne metody API | preconnect(...) |
prefetchUrlAsync(...) |
prerenderUrlAsync(...) |
| Konfiguracja | Nie dotyczy | PrefetchCache.setMaxPrefetches()PrefetchCache.setPrefetchTtlSeconds() |
setMaxPrerenders() |
| Wykorzystanie zasobów | Niskie (sieć) | Średnie (sieć, pamięć) | Wysokie (procesor, pamięć, sieć) |
| Kiedy używać | Gdy znane jest źródło docelowe, ale konkretny adres URL nie został jeszcze określony. | Gdy znany jest dokładny adres URL i prawdopodobna jest nawigacja, a buforowanie jest wspólne dla wszystkich komponentów WebView. | Gdy znany jest dokładny adres URL i nawigacja jest bardzo prawdopodobna w konkretnym komponencie WebView. |
| Korzyści | Szybsze konfigurowanie połączenia z dowolnym adresem URL w źródle | Szybsze wczytywanie sieci w przypadku pasujących adresów URL | Naprawdę natychmiastowa nawigacja po aktywacji |
Łączenie z wyprzedzeniem ze źródłami
Łączenie z wyprzedzeniem przyspiesza przyszłe wczytywanie, ponieważ z wyprzedzeniem wykonuje wyszukiwanie DNS i uzgadnianie połączenia TCP/TLS dla określonego źródła.
W przeciwieństwie do funkcji Prefetch i Prerender, które wymagają dokładnego docelowego adresu URL, funkcja Preconnect jest oparta wyłącznie na źródle. Dzięki temu możesz wywołać funkcję Preconnect znacznie wcześniej niż Prefetch i Prerender.
Ta strategia na poziomie profilu, która wymaga niewielkich zasobów, zmniejsza początkowe opóźnienie w przypadku każdego komponentu WebView udostępniającego ten profil, o ile źródło nie zostało jeszcze odwiedzone. Połączenie pozostaje otwarte przez około 30 sekund, co ułatwia wykonywanie kolejnych żądań HTTP, nawigacji i zasobów podrzędnych ze współdzieleniem, ponieważ eliminuje narzut związany z uzgadnianiem połączenia.
Implementacja
Aby zainicjować połączenie z wyprzedzeniem, wywołaj metodę preconnect(String url) w instancji Profile. Ten interfejs API musi być wywoływany w wątku UI i wymaga obsługi funkcji WebViewFeature.PRECONNECT.
Interfejs API działa na poziomie źródła, ale dla wygody można podać pełny adres URL
(np. https://www.example.com/index.html). Jest to automatycznie traktowane jako
wywołanie źródła (np. https://www.example.com). Można połączyć się z wieloma źródłami
, wywołując ten interfejs API kilka razy.
Kotlin
// Must be called on the @UiThread
if (WebViewFeature.isFeatureSupported(WebViewFeature.PRECONNECT)) {
profile.preconnect("https://www.example.com/index.html")
// This initiates a connection to the origin https://www.example.com
}
Java
// Must be called on the @UiThread
if (WebViewFeature.isFeatureSupported(WebViewFeature.PRECONNECT)) {
profile.preconnect("https://www.example.com/index.html");
// This initiates a connection to the origin https://www.example.com
}
Typowa konfiguracja: PrefetchParameters i PrerenderParameters
Zarówno funkcja Prefetch, jak i Prerender używają parametrów PrefetchParameters lub PrerenderParameters do dostosowywania żądania. Te klasy umożliwiają podawanie
dodatkowych nagłówków i wskazówek dotyczących dopasowywania adresów URL, np. konfiguracji No-Vary-Search.
Kotlin
// Isolated configuration specifically for Cache-Level Prefetching
val prefetchParams = PrefetchParameters.Builder()
.addAdditionalHeader("X-Custom-Client", "Android-App-V2")
.setExpectedNoVarySearchHeader(
NoVarySearchHeader.varyExcept(true, listOf("session_id", "click_ref"))
)
.build()
Java
PrefetchParameters prefetchParams = new PrefetchParameters.Builder()
.addAdditionalHeader("X-Custom-Header", "value")
/**
* Hint to ignore specific query parameters during cache matching.
* This allows the cache to match even if the tracking_id differs.
*/
.setExpectedNoVarySearchHeader(
NoVarySearchHeader.varyExcept(true, Arrays.asList("tracking_id"))
)
/**
* Determines if Client Hints are sent.
* NOTE: This is ignored for Prerendering API requests, which default to
* the WebView's WebSettings.getJavaScriptEnabled() value.
*/
.setJavaScriptEnabled(true)
.build();
Pobieranie treści z wyprzedzeniem
Pobieranie z wyprzedzeniem pobiera główny zasób HTML adresu URL i zapisuje go w pamięci podręcznej sieci profilu. W WebView Profile działa jako kontener danych przeglądarki, w tym plików cookie, pamięci podręcznej HTTP i skryptów service worker. Ponieważ pobieranie z wyprzedzeniem jest operacją na poziomie profilu, każdy komponent WebView powiązany z tym profilem może korzystać z odpowiedzi z pamięci podręcznej.
Implementacja
Aby zainicjować pobieranie z wyprzedzeniem, wywołaj metodę prefetchUrlAsync() w instancji Profile. Ta operacja obsługuje tylko schemat HTTPS.
Kotlin
profile.prefetchUrlAsync(
url,
prefetchParams,
cancellationSignal,
executor,
object : WebViewOutcomeReceiver<PrefetchResult, PrefetchException> {
override fun onResult(result: PrefetchResult) {
if (result.wasDuplicate()) {
// URL and No-Vary-Search permutations already exist in the cache layer
} else {
// The HTML payload has been successfully secured in the HTTP cache
}
}
override fun onError(error: PrefetchException) {
when (error) {
is PrefetchNetworkException -> {
// Isolates network layer or server-side HTTP anomalies
val code = error.httpStatusCode
// Facilitates rapid diagnosis of 4xx or 5xx server responses
}
else -> {
// Catches generalized execution failures and system constraints
}
}
}
}
)
Java
profile.prefetchUrlAsync(
url,
prefetchParams,
cancellationSignal,
executor,
new WebViewOutcomeReceiver<PrefetchResult, PrefetchException>() {
@Override
public void onResult(PrefetchResult result) {
if (result.wasDuplicate()) {
// URL and No-Vary-Search permutations already exist in the cache layer
} else {
// The HTML payload has been successfully secured in the HTTP cache
}
}
@Override
public void onError(PrefetchException error) {
if (error instanceof PrefetchNetworkException) {
// Isolates network layer or server-side HTTP anomalies
int code = ((PrefetchNetworkException) error).httpStatusCode;
// Facilitates rapid diagnosis of 4xx or 5xx server responses
} else {
// Catches generalized execution failures and system constraints
}
}
}
);
Cykl życia przechwytywania
Żądanie pobierania z wyprzedzeniem WebView zmienia moment i sposób wywoływania wywołania zwrotnego shouldInterceptRequest(). Ponieważ ma to bezpośredni wpływ na to, czy pobrana z wyprzedzeniem treść zostanie użyta, ważne jest, aby zrozumieć 2-etapowy cykl życia:
1. Faza spekulatywna (żądanie pobierania z wyprzedzeniem)
Gdy wywoływana jest metoda prefetchUrlAsync(), WebView pobiera w tle główny zasób HTML. W przypadku tego żądania w tle metoda shouldInterceptRequest() jest całkowicie pomijana. Żadna logika niestandardowa, tokeny autoryzacji ani wstawianie nagłówków, które są zwykle obsługiwane w przechwytywaniu, nie są stosowane do pobranego z wyprzedzeniem zasobu HTML.
2. Faza nawigacji (aktywacja użytkownika)
Gdy aplikacja wyraźnie przechodzi do adresu URL (np. za pomocą
WebViewCompat.navigate lub loadUrl) albo użytkownik kliknie pasujący
link, WebView sprawdza, czy może użyć pamięci podręcznej pobranej z wyprzedzeniem:
Ocena głównego kodu HTML: w tym momencie WebView wywoła metodę
shouldInterceptRequest()dla głównego kodu HTML. Aby strona była prawidłowo wyświetlana z pamięci podręcznej pobranej z wyprzedzeniem, przechwytywanie musi zwracać wartośćnull. Jeśli zwrócisz niestandardową wartośćWebResourceResponse, WebView będzie respektować przechwytywanie i całkowicie pominie pamięć podręczną pobraną z wyprzedzeniem.Ocena zasobów podrzędnych: gdy pobrany z wyprzedzeniem kod HTML jest gotowy do użycia, metoda
shouldInterceptRequest()jest wywoływana normalnie w przypadku wszystkich kolejnych zasobów podrzędnych (np. obrazów, skryptów i CSS) wymaganych do zakończenia renderowania strony.
Najważniejsze zachowania
Te cechy operacyjne i kontrole kwalifikowalności określają, jak WebView inicjuje żądania pobierania z wyprzedzeniem i nimi zarządza:
- Bezpieczeństwo wątków: żądania można inicjować z dowolnego wątku.
- Kwalifikowalność: przed zainicjowaniem pobierania WebView sprawdza, czy żądanie jest bezpieczne i odpowiednie kontekstowo, wykonując te czynności:
- Istniejące pliki cookie: aby chronić prywatność użytkowników i zapobiegać efektom ubocznym podobnym do CSRF, WebView może pominąć pobieranie z wyprzedzeniem, jeśli żądanie wymaga określonych uwierzytelnionych plików cookie, które mogą spowodować zmianę stanu na serwerze.
- Obecność skryptu service worker: jeśli skrypt service worker kontroluje już zakres adresu URL, WebView może przekazać obsługę pobierania do skryptu service worker zamiast inicjować standardowe pobieranie z wyprzedzeniem w sieci.
- Dostępność serwera proxy: WebView sprawdza, czy bieżąca ścieżka sieciowa (w tym wszystkie skonfigurowane serwery proxy) jest stabilna, aby uniknąć niepowodzenia żądań spekulatywnych w złożonych konfiguracjach sieci.
- Jeśli nie uda się rozpocząć pobierania z wyprzedzeniem (nawet przy prawidłowych parametrach), często dzieje się tak, ponieważ WebView stwierdził, że żądanie w tle może zakłócić bieżącą sesję użytkownika lub stan bezpieczeństwa.
- Anulowanie: użyj
CancellationSignal, aby zakończyć żądanie w trakcie realizacji i uniemożliwić jego buforowanie.
Renderowanie wstępne stron
Renderowanie wstępne tworzy ukryte „treści z internetu”, aby w pełni renderować stronę w tle, w tym wykonywać skrypty i pobierać zasoby podrzędne. Wstępne renderowanie korzysta z tej samej infrastruktury co pobieranie z wyprzedzeniem. Jeśli aplikacja zainicjuje wstępne renderowanie, WebView najpierw pobierze z wyprzedzeniem odpowiedź, aby wyświetlić nawigację wstępnego renderowania, unikając zbędnej aktywności sieciowej.
Implementacja
Wstępne renderowanie to operacja na poziomie instancji WebView. Wywołaj metodę prerenderUrlAsync() za pomocą WebViewCompat z wątku UI.
Kotlin
WebViewCompat.prerenderUrlAsync(
webView,
url,
cancellationSignal,
executor,
params,
object : PrerenderOperationCallback {
override fun onPrerenderActivated() {
// Called when the user navigates to the URL and the hidden page is swapped in
}
override fun onError(exception: Throwable) {
// exception is an instance of PrerenderException
// Handle prerender failure (for example, memory pressure or disallowed JavaScript APIs)
}
}
)
Java
WebViewCompat.prerenderUrlAsync(webView, url, cancellationSignal, executor, params, new PrerenderOperationCallback() {
@Override
public void onPrerenderActivated() {
// Called when the user navigates to the URL and the hidden page is swapped in.
}
@Override
public void onError(@NonNull Throwable exception) {
// Handle prerender failure (for example, resource constraints or disallowed APIs).
}
});
Zarówno pobieranie z wyprzedzeniem, jak i wstępne renderowanie są w pełni asynchroniczne. Metodę prefetchUrlAsync() można wywołać z dowolnego wątku, a metodę prerenderUrlAsync() trzeba zainicjować z wątku UI.
Ograniczenia techniczne
Aby zrównoważyć natychmiastową nawigację ze stanem systemu, WebView wymusza te ograniczenia czasu działania:
- Niedobór pamięci: WebView anuluje wstępnie renderowane adresy URL, jeśli na urządzeniu brakuje pamięci RAM.
- Niedozwolone interfejsy API: wszelkie próby uzyskania przez JavaScript dostępu do niektórych interfejsów API (np. odtwarzania dźwięku, alertów) w kontekście w tle natychmiast zakończą wstępne renderowanie.
- Limit instancji: istnieje limit aktywnych wstępnie renderowanych adresów URL dozwolonych w każdym komponencie WebView.
Dopasowywanie adresów URL i No-Vary-Search (NVS)
WebView wymaga niezawodnego algorytmu dopasowywania, aby zapewnić, że wstępnie wczytany zasób będzie wyświetlany tylko w przypadku zamierzonej nawigacji.
Dopasowanie ścisłe a dopasowanie NVS
Domyślnie pobieranie z wyprzedzeniem i wstępne renderowanie wymagają dokładnego dopasowania adresu URL. Jeśli adres URL, do którego nastąpiło przejście, jest identyczny z wstępnie wczytanym adresem URL, jest on natychmiast wyświetlany z pamięci podręcznej. Jeśli parametry zapytania się różnią, WebView używa tych reguł No-Vary-Search (NVS):
- Wskazówka: deweloperzy podają wskazówkę
setExpectedNoVarySearchHeader()podczas inicjowania. Jeśli adres URL, do którego nastąpiło przejście, pasuje do adresu URL żądania bez wskazanych parametrów, WebView na krótko blokuje się, aby poczekać na rzeczywiste nagłówki serwera. - Nagłówek serwera: ostateczną decyzję podejmuje nagłówek odpowiedzi NVS z serwera. Jeśli serwer potwierdzi, że różnice w zapytaniu należy zignorować, dopasowanie zostanie wyświetlone z pamięci podręcznej. W przeciwnym razie WebView wróci do wczytywania z sieci.
No-Vary-Search (NVS) jest przeznaczony do zaawansowanego użytku i większość deweloperów może go nie potrzebować
, ponieważ przekazują ten sam adres URL zarówno do pobierania z wyprzedzeniem, jak i do nawigacji
(WebViewCompat.navigate lub loadUrl). Te wskazówki są potrzebne tylko
wtedy, gdy występują różnice w parametrach zapytania między adresem URL pobierania z wyprzedzeniem
a adresem URL, do którego nastąpiło przejście.
Konfiguracja globalna
Dostosuj działanie ładowania spekulacyjnego na poziomie profilu, konfigurując limity PrefetchCache i maksymalną liczbę wstępnych renderowań. Możesz też przywrócić niestandardowe limity pobierania z wyprzedzeniem do ustawień domyślnych systemu:
Kotlin
// Configure prefetch cache limits
profile.prefetchCache.setMaxPrefetches(10)
profile.prefetchCache.setPrefetchTtlSeconds(60)
// Reset to system defaults when needed
profile.prefetchCache.clearMaxPrefetches()
// Configure maximum active prerenders
profile.setMaxPrerenders(2)
Java
// Configure prefetch cache limits
PrefetchCache prefetchCache = profile.getPrefetchCache();
prefetchCache.setMaxPrefetches(10);
prefetchCache.setPrefetchTtlSeconds(60);
// Reset to system defaults when needed
prefetchCache.clearMaxPrefetches();
// Configure maximum active prerenders
profile.setMaxPrerenders(2);
Obsługa błędów i wyjątków
Operacje spekulatywne używają OutcomeReceiverCompat lub PrerenderOperationCallback do raportowania wyników.
Główne wyjątki
Gdy operacja ładowania spekulacyjnego nie powiedzie się, procedura obsługi błędów zgłasza jeden z tych głównych typów wyjątków, aby pomóc Ci zdiagnozować konkretne scenariusze niepowodzenia:
PrefetchException: klasa bazowa wszystkich asynchronicznych błędów pobierania z wyprzedzeniem.PrefetchNetworkException: wskazuje błąd na poziomie sieci lub serwera. Może zawierać polehttpStatusCode(np. 404 lub 503), które pomaga w diagnozowaniu problemów po stronie serwera.PrerenderException: klasa nadrzędna wszystkich błędów związanych z wstępnym renderowaniem, takich jak błędy spowodowane niedoborem pamięci lub użyciem niedozwolonych interfejsów API (np. odtwarzania dźwięku) w tle.
Strategie optymalizacji
Aby zmaksymalizować korzyści z ładowania spekulacyjnego przy jednoczesnym oszczędzaniu zasobów systemowych, postępuj zgodnie z tymi zaleceniami:
- Inicjuj wcześnie: rozpocznij pobieranie z wyprzedzeniem podczas uruchamiania aplikacji lub gdy tylko prawdopodobne jest miejsce docelowe nawigacji.
- Zintegrowana strategia: jeśli wstępnie renderujesz adres URL, który jest już w pamięci podręcznej pobranej z wyprzedzeniem, nawigacja wstępnego renderowania jest wyświetlana z tej pamięci podręcznej, co pozwala uniknąć zbędnych żądań sieciowych.
- Monitoruj limity: wstępne renderowanie wymaga dużej ilości zasobów. W przypadku kilku prawdopodobnych kandydatów preferuj pobieranie z wyprzedzeniem, a wstępne renderowanie rezerwuj dla najbardziej prawdopodobnej nawigacji.
- Obsługa schematów: upewnij się, że wszystkie adresy URL używają obowiązkowego schematu HTTPS. Nieprawidłowe schematy lub dane wejściowe o wartości null powodują synchroniczny wyjątek
IllegalArgumentException.
Dodatkowe materiały
Więcej informacji o debugowaniu aplikacji internetowych, optymalizowaniu wydajności uruchamiania WebView i obsłudze zakończenia procesu renderowania znajdziesz w tych materiałach:
- Ulepszona nawigacja na stronie za pomocą metody
WebViewCompat.navigate - Debugowanie aplikacji internetowych
- Optymalizowanie uruchamiania WebView
- Obsługa zakończenia procesu renderowania WebView