WebView uruchamia kod natywny w wielu procesach, aby renderować treści internetowe
w aplikacji na Androida. Pozostawienie instancji WebView bez zarządzania może prowadzić do wycieków pamięci, awarii z powodu braku pamięci (OOM) i pogorszenia wydajności aplikacji.
Z tego dokumentu dowiesz się, jak działa model pamięci WebView w przypadku wielu procesów, jak prawidłowo zarządzać cyklem życia, aby zapobiegać wyciekom, oraz jak diagnozować problemy z pamięcią.
Architektura pamięci WebView
Aby skutecznie zarządzać pamięcią WebView, musisz wiedzieć, jak Android przydziela zasoby na potrzeby treści internetowych:
Wykonywanie w wielu procesach: w Androidzie 8.0 (poziom API 26) i nowszych wersjach
WebViewoddziela treści internetowe od podstawowych funkcji aplikacji w wielu procesach (na urządzeniach z małą ilością pamięci RAM może wrócić do jednego procesu):- Proces hosta (przeglądarki): główny proces aplikacji, w którym działa kod
Activityoraz kod Java lub Kotlin. - Izolowany proces renderujący: oddzielny proces w piaskownicy (
SandboxedProcessService), który analizuje kod HTML i CSS, wykonuje kod JavaScript i renderuje strony internetowe.
- Proces hosta (przeglądarki): główny proces aplikacji, w którym działa kod
Wykorzystanie pamięci natywnej: większość
WebViewpamięci, w tym renderowana grafika, drzewo DOM i pamięć środowiska wykonawczego JavaScript, jest przydzielana w pamięci natywnej, a nie na stercie Java. Zrzut sterty Java (.hprof) pokazuje tylko lekki obiekt otoki Java i nie rejestruje rzeczywistej ilości pamięci używanej przez treści internetowe.Wpływ pamięci natywnej na system: w przeciwieństwie do alokacji na stercie Java, które są ograniczone limitem
maxHeapaplikacji i szybko kończą się błędemOutOfMemoryError, pamięć natywna może cicho rosnąć do gigabajtów. Gdy niezwolniona pamięć natywna wypełnia pamięć RAM i przestrzeń wymiany (zRAM), mechanizm Low Memory Killer (LMK) w Androidzie zaczyna zamykać procesy działające w tle, aby odzyskać pamięć. Powoduje to pogorszenie ogólnej wielozadaniowości urządzenia, a w końcu zamknięcie aplikacji działającej na pierwszym planie.
Zarządzanie cyklem życia WebView
Prawidłowe zarządzanie cyklem życia jest niezbędne do zapobiegania wyciekom pamięci. Częstym błędem jest założenie, że usunięcie WebView z układu lub automatyczne zakończenie działania Activity zwalnia jego pamięć.
Aby zapewnić całkowite wyczyszczenie zarówno odwołań do kontekstu Java, jak i natywnych zasobów renderowania, musisz wyraźnie zorganizować sekwencję zamykania w cyklu życia komponentu hosta (np. onDestroy()), zatrzymując wykonywanie aktywnej strony, odłączając widok od kontenera i zwalniając natywne powiązania.
Zwalnianie miejsca w instancjach WebView
Aby zapewnić prawidłowe zamknięcie i zwolnienie zasobów po zniszczeniu Activity lub Fragment, wykonaj te czynności:
- Usuń
WebViewz kontenera nadrzędnego (ViewGroup). - Zatrzymaj aktywne wczytywanie i wyczyść historię nawigacji.
- Wywołaj metodę
destroy(). - Wyczyść odwołanie do
null.
Poniższy przykład pokazuje, jak prawidłowo zwolnić miejsce w WebView:
Kotlin
override fun onDestroy() {
myWebView?.let {
// Remove the WebView from its parent ViewGroup.
(it.parent as? ViewGroup)?.removeView(it)
// Stop active loading and clear history.
it.stopLoading()
it.clearHistory()
// Destroy the instance.
it.destroy()
}
myWebView = null
super.onDestroy()
}
Java
@Override
protected void onDestroy() {
if (myWebView != null) {
// Remove the WebView from its parent ViewGroup.
if (myWebView.getParent() instanceof ViewGroup) {
((ViewGroup) myWebView.getParent()).removeView(myWebView);
}
// Stop active loading and clear history.
myWebView.stopLoading();
myWebView.clearHistory();
// Destroy the instance.
myWebView.destroy();
}
myWebView = null;
super.onDestroy();
}
Pamięć po zniszczeniu
Gdy wywołasz metodę destroy(), system zwolni kontekst Activity, wyczyści hierarchie widoków i zatrzyma pracę w tle w internecie. Możesz jednak zauważyć, że pamięć fizyczna procesu (rozmiar zbioru roboczego) nie spadnie od razu do poziomu sprzed użycia `WebView`.
To normalne zjawisko. Pamięć podręczna środowiska wykonawczego, biblioteki współdzielone i przydzielone strony pamięci pozostają w procesie, dopóki system operacyjny ich nie odzyska lub proces się nie zakończy. Głównym celem metody destroy() jest zapobieganie kumulatywnym wyciekom pamięci Activity, gdy użytkownicy przechodzą między ekranami opartymi na internecie.
Kluczowe dane debugowania
Podczas analizowania zużycia pamięci przez WebView skup się na tych danych:
Rozmiar zbioru roboczego (RSS): łączna ilość pamięci RAM przypisana do procesu, w tym kod i biblioteki współdzielone (w Profilerze Android Studio oznaczona jako Łącznie).
Anonimowy rozmiar zbioru roboczego (RssAnon): pamięć przydzielona bezpośrednio przez proces, która nie jest przechowywana w pliku na dysku (np. sterta natywna i alokacje środowiska wykonawczego JavaScript). Reprezentuje ona główny koszt pamięci treści internetowych (w Profilerze Android Studio oznaczona jako Przydzielona).
Zajętość pamięci prywatnej (PMF): suma anonimowego rozmiaru zbioru roboczego i przestrzeni wymiany (zRAM). PMF odzwierciedla rzeczywiste obciążenie pamięci, którego nie można usunąć, a które aplikacja nakłada na system.
PMF przeglądarki a PMF renderera: pamięć używana przez główny proces aplikacji a pamięć używana przez izolowany proces renderujący. Duże treści internetowe powodują skoki zużycia pamięci głównie w procesie renderującym.
Liczba aktywnych obiektów (
WebViews,Activities,Views): liczba aktywnych instancji interfejsu, kontekstu iWebViewprzechowywanych w pamięci. Śledzenie tych danych pozwala określić, czy wzrost zużycia pamięci jest spowodowany zachowanymi odwołaniami Java, czy alokacjami tylko natywnymi.Pamięć prywatna i sterta natywna: w
dumpsys meminfoalokacje natywne C/C++ i niestandardowe mapowania pamięci (np.PartitionAllocChromium lub sterty osadzonego środowiska wykonawczego JavaScript) pojawiają się w sekcji Native Heap i Private Other, a nie w Java Heap.
Więcej informacji o licznikach pamięci procesu i ich kategoriach znajdziesz w glosariuszu pamięci procesu.
Praktyczne procedury diagnostyczne
Ponieważ WebView działa w wielu procesach i przydziela pamięć natywną, do sprawdzenia jego zajętości użyj tych narzędzi i technik:
Narzędzia do profilowania i diagnostyki
Aby sprawdzić alokacje pamięci i zdiagnozować wycieki, użyj tych narzędzi:
Profiler pamięci w Android Studio: używaj Profilera pamięci do wizualizowania alokacji natywnych, śledzenia kategorii pamięci w czasie i wykrywania
Activitypodczas przejść między ekranami.Śledzenie pamięci za pomocą Perfetto: używaj Perfetto do rejestrowania liczników pamięci na poziomie systemu (np. RSS i anonimowy RSS), aby obserwować ogólny wzrost zużycia pamięci . Pamiętaj, że
WebViewalokacje natywnego silnika nie generują stosów wywołań w narzędziu do profilowania sterty Perfetto. Używaj Narzędzi deweloperskich w Chrome, aby sprawdzać zrzuty sterty JavaScript i alokacje DOM w treściach internetowych.
Sprawdzanie liczby aktywnych obiektów
Aby określić, czy wzrost zużycia pamięci jest spowodowany zachowanymi obiektami platformy Java (np. komponentami interfejsu) czy alokacjami natywnymi, sprawdź sekcję Objects
w dumpsys meminfo:
adb shell dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"Dane wyjściowe pokazują liczbę aktywnych obiektów:
Objects
Views: 142 ViewRootImpl: 1
AppContexts: 3 Activities: 1
Assets: 12 AssetManagers: 0
Local Binders: 32 Proxy Binders: 45
Parcel memory: 15 Parcel count: 30
Death Recipients: 2 WebViews: 1
Ta sekcja zawiera liczbę aktywnych obiektów frameworka, uchwytów IPC i alokacji Parcel. W przypadku diagnostyki WebView skup się przede wszystkim na Activities
i WebViews.
Powtarzaj interakcję użytkownika (np. otwieranie i zamykanie ekranu internetowego) i porównuj liczby:
Wyciek instancji: jeśli liczba
WebViewslubActivitieszwiększa się przy każdej nawigacji i nie wraca do wartości bazowej, aplikacja powoduje wyciek instancjiWebViewJava lubActivityhosta (np. z powodu brakuViewGroup.removeView()lub zachowanych odwołań do odbiornika). Ponieważ wyciekActivityprzypina całe drzewo widoków i zdekodowane zasoby obrazów w pamięci, powtarzające się wizyty szybko wyczerpują stertę Java i powodują awarieOutOfMemoryError.Wyciek natywny lub DOM: jeśli
WebViewsiActivitiespozostają stałe, a łączny rozmiar zbioru roboczego procesu i Private Other nadal rosną, wyciek pochodzi z niezwolnionych zasobów natywnych, elementów DOM lub powiązań silnika JavaScript. Ponieważ te alokacje znajdują się w pamięci natywnej i omijają moduł zbierania śmieci ART, pozostają niewidoczne dla standardowych narzędzi do wykrywania wycieków Java i nadal się gromadzą, dopóki system operacyjny nie zakończy działania aplikacji.
Profilowanie izolowanego procesu renderującego za pomocą interfejsu wiersza poleceń
Uruchomienie dumpsys meminfo z nazwą pakietu aplikacji powoduje wyświetlenie tylko pamięci głównego procesu hosta. Aby sprawdzić izolowany proces renderujący, w którym renderowane są strony internetowe:
Znajdź identyfikator procesu (PID) izolowanej usługi renderującej:
adb shell dumpsys activity processes <var>PACKAGE_NAME</var> | grep "Isolated.*SandboxedProcessService"Dane wyjściowe pokazują rekord izolowanego procesu i jego PID RENDERER_PID (np.
22155):Isolated #5: ProcessRecord{... 22155:com.google.android.webview.debug:sandboxed_process0:...}Sprawdź podział pamięci procesu renderującego za pomocą jego PID:
adb shell dumpsys meminfo <var>RENDERER_PID</var>Sprawdź proces aplikacji hosta, aby ocenić zajętość po stronie przeglądarki:
adb shell dumpsys meminfo <var>PACKAGE_NAME</var>
Sprawdzanie map pamięci i alokacji
Aby sprawdzić, które podsystemy natywne lub alokatory zajmują pamięć anonimową, sprawdź mapy pamięci procesu:
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"W tabeli poniżej znajdziesz listę typowych tagów pamięci anonimowej i ich znaczenie dla wzrostu zużycia pamięci:
| Tag pamięci | Podsystem | Znaczenie dla aplikacji i treści internetowych | Częsta przyczyna wzrostu zużycia pamięci? |
|---|---|---|---|
[anon:partition_alloc] |
Chromium PartitionAlloc | Alokacje dla drzew DOM, buforów renderowania, sterty JavaScript V8 i wykonywania WebAssembly w WebView. |
Tak (wysokie): wczytywanie dużych stron internetowych, bogatych w multimedia drzew DOM lub brak wywołania metody destroy() w przypadku odrzuconych instancji WebView bezpośrednio zwiększa wartość tego tagu. |
[anon:scudo...] lub [anon:libc_malloc] |
Alokatory sterty natywnej Androida (Scudo / jemalloc) | Ogólne alokacje natywne C/C++ używane przez biblioteki NDK, mostki JNI i natywne potoki graficzne. | Tak (średnie do wysokiego): wzrost występuje, gdy natywne otoki JNI lub zależności C++ innych firm zachowują niezwolnione alokacje podczas nawigacji. |
[anon:...] (np. [anon:quickjs_heap...]) |
Skrypty niestandardowe lub natywne środowiska wykonawcze | Osadzone silniki JavaScript, niestandardowe środowiska wykonawcze WebAssembly lub niestandardowe pule buforów natywnych. | Tak (zależne od kontekstu): często spotykane w aplikacjach hybrydowych, które wykonują silniki skryptów obok widoków natywnych i nie czyszczą powiązań środowiska wykonawczego. |
Ograniczenia interfejsów API pamięci w aplikacji
Interfejsy API pamięci w aplikacji (np. Debug.getMemoryInfo lub
ActivityManager.getProcessMemoryInfo) mierzą tylko proces wywołujący.
W trybie wieloprocesowym te interfejsy API nie mogą rejestrować pamięci zużywanej przez izolowany proces renderujący. Aby uzyskać dokładną ocenę całkowitej ilości pamięci, używaj narzędzi systemowych, takich jak dumpsys meminfo, Perfetto lub Profiler Android Studio.
Triage dużej ilości pamięci w aplikacji hybrydowej
Podczas diagnozowania niewyjaśnionego wzrostu zużycia pamięci podczas powtarzających się interakcji z WebView (np. otwierania linków internetowych lub przeglądania kanałów opartych na internecie) użyj tego procesu triage, aby sprawdzić, czy wyciek pochodzi z warstwy Java, czy z silnika natywnego:
Izolowanie typu wycieku (Java a natywny): Uruchom
dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"przed i po powtarzających się przejściach użytkownika (np. otwieraniu i zamykaniu artykułów internetowych lub przesuwaniu palcem po kanałach).- Obserwacja: jeśli liczba
ActivitiesiWebViewspozostaje stała (np. 1–2 aktywne instancje), aplikacja nie powoduje wycieku kontekstówActivityani instancjiWebViewJava.
- Obserwacja: jeśli liczba
Mierzenie delty pamięci podczas interakcji (śledzenie szeregów czasowych): rejestruj zrzuty
dumpsys meminfopodczas wielu interakcji użytkownika, aby obliczyć współczynnik alokacji na przejście:- Obserwacja: sterta Java pozostaje ograniczona i w dobrym stanie (wzrasta podczas używania i spada po zebraniu śmieci), ale Private Other i Native Heap stale rosną o kilka megabajtów na przejście. Dowodzi to, że wyciek występuje w całości w pamięci natywnej poza środowiskiem wykonawczym ART.
Standardowe zrzuty sterty Java (
.hprof) nie wykażą żadnych problemów.
- Obserwacja: sterta Java pozostaje ograniczona i w dobrym stanie (wzrasta podczas używania i spada po zebraniu śmieci), ale Private Other i Native Heap stale rosną o kilka megabajtów na przejście. Dowodzi to, że wyciek występuje w całości w pamięci natywnej poza środowiskiem wykonawczym ART.
Standardowe zrzuty sterty Java (
Sprawdzanie map pamięci anonimowej: sprawdź mapy pamięci procesu za pomocą ADB (patrz Sprawdzanie map pamięci i alokacji):
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"- Obserwacja: wzrost zużycia pamięci koncentruje się w
[anon:partition_alloc]lub osadzonych stertach silnika skryptów, czemu towarzyszy powolny wzrost globalnych odwołań JNI. Wskazuje to, że chociaż widoki Java zostały zastąpione, podstawowe natywne obiekty strony lub powiązania JavaScript nie zostały zwolnione.
- Obserwacja: wzrost zużycia pamięci koncentruje się w
Działania naprawcze:
- Upewnij się, że każdy odzyskany lub odrzucony
WebViewwyraźnie zatrzymuje aktywne skrypty (stopLoading()), czyści historię i wywołuje metodędestroy(). - Zamykaj niestandardowe wywołania zwrotne mostka JavaScript lub globalne odwołania JNI powiązane z odrzuconymi widokami.
- Sprawdź, czy po przejściach nawigacyjnych stabilizują się wartości
Private Otheri rozmiaru zbioru roboczego procesu.
- Upewnij się, że każdy odzyskany lub odrzucony
Dodatkowe materiały
Więcej informacji o debugowaniu i profilowaniu pamięci oraz wydajności WebView znajdziesz w tych materiałach: