Procesy aplikacji na Androidzie nie działają w izolacji. Aplikacje często korzystają z usług świadczonych przez inne aplikacje lub sam system. Gdy jeden proces łączy się z innym za pomocą powiązania usługi, tworzy zależność, która ma duży wpływ na sposób zarządzania pamięcią przez platformę Android.
Stany procesów i wyniki OOM
Platforma Androida używa stanów procesów do śledzenia ważności każdego uruchomionego procesu. Te stany są następnie używane przez OomAdjuster do przypisywania wartości korekty wyniku OOM (oom_score_adj) w zakresie od -1000 do 1000.
Niższa wartość oom_score_adj oznacza, że proces jest ważniejszy i mniej prawdopodobne jest, że zostanie zakończony przez mechanizm LMK (Low Memory Killer).
Typowe stany procesu
W tabeli poniżej znajdziesz niektóre z najczęstszych stanów procesu i ich typowe wartości oom_score_adj. Pełną i aktualną listę znajdziesz w android.app.ActivityManager i com.android.server.am.psc.Constants w kodzie źródłowym Androida.
| Stan procesu (skrót) | Opis | Zwykle (oom_score_adj) |
|---|---|---|
| PER (Persistent) | procesy systemowe, które muszą być zawsze uruchomione (np. telefon). | -800 |
| TOP | Proces, z którym użytkownik obecnie wchodzi w interakcję. | 0 |
| VIS (widoczny) | Proces ma widoczną aktywność (np. za półprzezroczystym oknem). | 100 |
| PERC (Perceptible) | proces w tle, o którym użytkownik wie (np. odtwarzanie muzyki); | 200 |
| FGS | Proces hostujący usługę działającą na pierwszym planie. | 0–200 (zależy od przypadku) |
| BTOP (Bound Top) | Proces powiązany z aplikacją TOP. | 100 |
| BFGS | Usługa na pierwszym planie z powiązanym komponentem (zwykle powiązana z systemem). | 0 |
| PREV (Wstecz) | Ostatni proces, w którym użytkownik był przed bieżącym. | 700 |
| CACHED | Aplikacje działające w tle, które można bezpiecznie zamknąć. | 900–999 |
Wpływ powiązań usługi
Gdy proces klienta (np. aplikacja w stanie TOP) wiąże się z usługą w procesie serwera, proces serwera często dziedziczy wyższy priorytet. Dzięki temu usługa pozostaje dostępna tak długo, jak jest potrzebna klientowi.

Kontrolowanie dziedziczenia za pomocą flag BIND
Dziedziczenie jest domyślnym zachowaniem podczas korzystania z funkcji Context.BIND_AUTO_CREATE.
Deweloperzy mogą jednak kontrolować, jak powiązanie wpływa na ważność procesu docelowego, za pomocą różnych flag w bindService().
Kluczowe flagi BIND dla wyniku OOM
Podczas zarządzania ogólnosystemowym obciążeniem pamięci najbardziej przydatne są te flagi:
BIND_AUTO_CREATE: najczęstsza flaga. Gwarantuje to, że proces usługi zostanie uruchomiony i będzie działać tak długo, jak długo istnieje powiązanie. Domyślnie podnosi też priorytet procesu serwera, aby był zgodny z priorytetem klienta.BIND_NOT_FOREGROUND: zapobiega przeniesieniu procesu usługi docelowej na pierwszy plan z harmonogramem priorytetu (priorytet procesora). Umożliwia jednak podniesienie priorytetu pamięci (oom_score_adj). Jest to przydatne w przypadku zadań w tle, które nie powinny konkurować z interfejsem użytkownika o cykle procesora, ale powinny być chronione przed zamknięciem.BIND_WAIVE_PRIORITY: bardzo silny sygnał, który nakazuje systemowi nie wpływać na priorytet planowania lub zarządzania pamięcią procesu docelowego. Proces usługi będzie zarządzany tak, jakby był zwykłym procesem w tle na liście LRU, co oznacza, że może zostać zakończony przez OOM nawet wtedy, gdy jest powiązany.BIND_ABOVE_CLIENT: oznacza, że usługa jest ważniejsza niż sama aplikacja klienta. Gdy system musi odzyskać pamięć, woli zamknąć aplikację klienta niż powiązaną usługę. Jest to „silniejsza” ochrona niżBIND_AUTO_CREATE, ponieważ zapewnia dodatkową warstwę zabezpieczeń usługi kosztem klienta.BIND_NOT_PERCEPTIBLE: obniża ważność usługi docelowej poniżej poziomuPERCEPTIBLE, co pozwala systemowi odzyskać pamięć i zwolnić miejsce na ważniejsze procesy widoczne dla użytkownika.
Praktyczne ćwiczenie: obserwowanie efektów wiązania
Aby pokazać, jak powiązanie z aplikacją TOP wpływa na stan oddzielnego procesu, użyjemy aplikacji MemoryLab.
1. Uruchamianie MemoryLab
Poniższe polecenie uruchamia aplikację. Po jej otwarciu upewnij się, że pozostaje ona na pierwszym planie (nie naciskaj jeszcze przycisku Home ani nie przełączaj aplikacji).
adb shell am start -n com.android.memorylab/.MainActivity
2. Identyfikowanie procesów
Przed powiązaniem sprawdź stany procesów. MemoryLab uruchamia główny interfejs w jednym procesie i ma RemoteService, który działa w procesie :remote.
adb shell dumpsys activity processes com.android.memorylab
Przykładowy fragment kodu wyjściowego:
Process OOM control (154 total, non-act at 7, non-svc at 7):
Proc #0: fg T/A/TOP LCMNFUATI t: 0 13470:com.android.memorylab/u0a417 (top-activity)
oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
state: cur=TOP set=TOP lastRss=0.00 lastCachedRss=0.00
Główny proces com.android.memorylab będzie miał stan TOP. Proces
:remote jeszcze się nie rozpoczął.
3. Powiązanie aktywatora
Wyślij do aplikacji transmisję, aby wywołać powiązanie usługi:
adb shell am broadcast -a com.android.memorylab.LEAK_BINDER
4. Obserwowanie stanu podwyższonego
Ponownie sprawdź stany procesów:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
Przykładowy fragment kodu wyjściowego:
Proc # 1: vis F/ /BTOP ---NFUATI t: 0 13560:com.android.memorylab:remote/u0a417 (service)
com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
oom: max=1001 curRaw=100 setRaw=100 cur=100 set=100
state: cur=BTOP set=BTOP lastRss=0.00 lastCachedRss=0.00
Proces :remote jest teraz uruchomiony i ma stan BTOP (Bound TOP) z wartością oom_score_adj równą 100. Jest to znacznie lepiej chronione niż typowa usługa działająca w tle (która miałaby poziom 500 lub wyższy). Symbol <=Proc{...} wskazuje, który proces jest odpowiedzialny za podniesienie priorytetu.
5. Przejdź do odtwarzania w tle
Naciśnij przycisk HOME na urządzeniu. Sprawdź jeszcze raz stany:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
Przykładowy fragment kodu wyjściowego:
Proc # 2: prev b/ /LAST --------I t: 0 13560:com.android.memorylab:remote/u0a417 (service)
com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
state: cur=LAST set=LAST lastRss=0.00 lastCachedRss=0.00
Proc # 1: prev b/ /LAST --------I t: 0 13470:com.android.memorylab/u0a417 (previous)
oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
state: cur=LAST set=LAST lastRss=209MB lastCachedRss=0.00
Oba procesy mają teraz niższy priorytet (PREV /
oom_score_adj 700), ponieważ proces klienta nie jest już TOP. (Uwaga: LAST w zrzucie stanu odnosi się do stanu wewnętrznego LAST_ACTIVITY, który w podsumowaniach wysokiego poziomu odpowiada PREV).
Analizowanie za pomocą narzędzia procstats
Narzędzie procstats umożliwia wyświetlanie tych stanów w ujęciu historycznym.
# View stats for MemoryLab over the last hour
adb shell dumpsys procstats --hours 1 com.android.memorylab
Przykładowy fragment kodu wyjściowego:
* com.android.memorylab / u0a417 / v37:
* Prc com.android.memorylab / u0a417 / v37:
TOTAL: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
Top: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
* Prc com.android.memorylab:remote / u0a417 / v37:
TOTAL: 0.19%
Bnd Top: 0.19%
Wartość Bnd Top oznacza procent czasu, przez jaki proces zdalny był powiązany z aplikacją w stanie TOP.
Rejestrowanie i analizowanie powiązań za pomocą Perfetto
dumpsys daje Ci migawkę, a Perfetto pozwala zobaczyć dokładny moment wystąpienia powiązania i jak zmienia się wynik OOM w czasie rzeczywistym.
1. Nagrywanie śladu
Użyj konfiguracji, która zawiera linux.process_stats i kategorię am atrace:
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/service_bindings.perfetto-trace <<EOF
buffers: { size_kb: 65536 }
data_sources: {
config {
name: "linux.process_stats"
process_stats_config { proc_stats_poll_ms: 100 }
}
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config { ftrace_events: "am/am_proc_bound" }
}
}
duration_ms: 15000
EOF
2. Zmiany wyniku błędu braku pamięci w zapytaniu
Za pomocą PerfettoSQL możesz sprawdzić, jak zmienił się wynik OOM procesu zdalnego w porównaniu z procesem interfejsu:
SELECT ts, p.name, value AS oom_score_adj
FROM counter c
JOIN process_counter_track t ON c.track_id = t.id
JOIN process p USING (upid)
WHERE p.name LIKE 'com.android.memorylab%'
AND t.name = 'oom_score_adj'
ORDER BY ts;
3. Identyfikowanie zdarzeń powiązania
Aby sprawdzić, kiedy dokładnie powstała zależność wiążąca i który proces ją zainicjował, użyj tego zapytania:
SELECT
s.ts,
p.name AS process_name,
t.name AS thread_name,
s.name AS slice_name
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread t USING (utid)
JOIN process p USING (upid)
WHERE s.name LIKE 'bindService:{com.android.memorylab%';
Powiązania systemowe z aplikacją
Sam system Android często łączy się z usługami w aplikacjach innych firm, aby zapewnić główną funkcjonalność. Celem tych powiązań jest często zmniejszenie opóźnienia. Utrzymując proces w pamięci, system unika kosztownego obciążenia związanego z uruchomieniem „na zimno” (wczytaniem pliku APK, zainicjowaniem środowiska wykonawczego i utworzeniem obiektu Application), gdy nastąpi krytyczna interakcja użytkownika. Istnieją inne powiązania, które zapobiegają częstemu uruchamianiu „na zimno” aplikacji, które muszą obsługiwać strumienie zdarzeń w tle.
Oto kilka przykładów z prawdziwego życia, które możesz zaobserwować na typowym urządzeniu:
VoiceInteractor
Użytkownicy oczekują, że asystent cyfrowy będzie wbudowany w system operacyjny telefonu, będzie można go natychmiast wywołać za pomocą wypowiedzianego słowa aktywującego lub szybkiego gestu, a interakcja będzie płynna i bezproblemowa.
Gdy nastąpi wywołanie asystenta (np. na telefonach Google Pixel zostanie wypowiedziane słowo aktywujące „OK Google”), asystent cyfrowy musi natychmiast zareagować. Aby to zapewnić,system_server utrzymuje stałe powiązanie z usługą interakcji głosowej wybraną przez użytkownika.

Jeśli sprawdzisz stany procesów (np. za pomocą dumpsys activity processes), możesz zobaczyć proces taki jak com.google.android.googlequicksearchbox:interactor w stanie BFGS (powiązany usługa działająca na pierwszym planie), który jest utrzymywany przy życiu przez powiązanie z system_server (UID 1000).
NotificationListenerService
W przypadku niektórych powiązań systemowych z aplikacjami celem nie jest opóźnienie, ale raczej zapobieganie częstym zimnym startom.
NotificationListenerService, usługa, która odbiera połączenia z systemu, gdy pojawiają się lub są usuwane nowe powiadomienia. Typowy użytkownik smartfona może otrzymywać setki powiadomień dziennie. Jeśli system odłączy się od odbiornika powiadomień, proces tej aplikacji prawdopodobnie przejdzie w stan buforowania i może zostać zakończony przez LMK.
Gdy nadejdzie kolejne powiadomienie (nawet po kilku sekundach), system będzie musiał ponownie uruchomić proces aplikacji, aby dostarczyć zdarzenie. Ten ciągły cykl zamykania i uruchamiania od nowa zużywałby znacznie więcej procesora i baterii niż po prostu utrzymywanie procesu w stanie powiązanym i aktywnym w tle.
Ekran –1 launchera (kanał informacyjny)
Nowoczesne aplikacje typu Launcher zwykle łączą podstawowe funkcje nawigacyjne (ikony i widżety na ekranie głównym) z kanałem wiadomości, który jest dostępny na jednym z ekranów Launchera i bezproblemowo zintegrowany z interfejsem Launchera. Kanał wiadomości może być dostarczany przez inną aplikację. Na przykład na urządzeniach Google Pixel Launcher jest zintegrowany z kanałem wiadomości dostarczanym przez aplikację Google.
Gdy przesuniesz palcem w lewo na ekranie głównym, aby wyświetlić kanał wiadomości, przejście musi być płynne. Launcher osiąga to, wiążąc się z interfejsem usługi w aplikacji, która udostępnia kanał wiadomości, i utrzymując to powiązanie tak długo, jak długo działa launcher. Dzięki temu zawartość karty jest renderowana i gotowa w pamięci, nawet gdy na nią nie patrzysz.
Inne typowe przykłady
- Menu z aplikacjami (HOME_APP_ADJ): aplikacja Menu z aplikacjami (Home) ma własne specjalne miejsce na liście priorytetów. Chociaż nie zawsze jest powiązany z usługą, ma przypisany numer
HOME_APP_ADJ(zwykle 600). System woli zachować aktywność launchera, ponieważ użytkownik często do niego wraca. System woli zamknąć wcześniej używaną aplikację (PREV_APP_ADJ = 700) niż launcher, ponieważ zamknięcie launchera spowoduje spowolnienie działania urządzenia podczas zamykania dowolnej aplikacji. Użytkownik będzie musiał poczekać na uruchomienie launchera „na zimno”. - Edytor do wprowadzania (IME): podczas pisania system łączy się z wybraną aplikacją klawiatury (np. Gboard). Dzięki temu proces klawiatury pozostaje w stanie podwyższonym nawet wtedy, gdy klawiatura jest tymczasowo ukryta. Dzięki temu klawiatura może pojawić się natychmiast po kliknięciu innego pola tekstowego.
- Płatności NFC: gdy dotkniesz telefonem, aby zapłacić, system połączy się z usługą płatności NFC (np. Portfelem Google). W przypadku tych transakcji terminal sprzedawcy często ma ścisłe wymagania dotyczące czasu rzeczywistego. Jeśli aplikacja płatnicza musiała zostać uruchomiona „na zimno”, transakcja mogła przekroczyć limit czasu i zakończyć się niepowodzeniem.
Kompromisy i nagły spadek wydajności
Wiązania są niezbędne do zapewnienia wydajności i poprawności, ale mają negatywny wpływ na stan pamięci systemu.
- Mniejsza elastyczność: każdy proces powiązany to proces, którego LMK nie może łatwo zakończyć. Zmniejsza to „bufor” procesów w pamięci podręcznej, których system może użyć do zwolnienia pamięci w przypadku jej niedoboru.
- Pogorszenie wydajności: jeśli zbyt wiele procesów jest powiązanych, system może mieć prawie żadnych procesów w tle, które można zakończyć. Gdy obciążenie pamięci wzrośnie, system znacznie szybciej „spadnie z klifu wydajności”, ponieważ będzie zmuszony do zamykania ważniejszych procesów lub do intensywnego korzystania z pamięci podręcznej stron.
Typowe antypatie powiązań usług
Powiązania usług bezpośrednio podnoszą poziom uprawnień oom_score_adj, dlatego drobne błędy w cyklu życia powiązań, które mogą wystąpić podczas ich uzyskiwania lub strukturyzowania, mogą przez wiele godzin utrzymywać duże ilości pamięci w stanach uprzywilejowanych (BTOP, BFGS lub PERC). Podczas projektowania lub sprawdzania usług powiązanych uważaj na te typowe anty-wzorce.
Zapomniane unbindService() w przypadku klientów o długim okresie życia
Powiązanie z usługą z pojedynczego obiektu Application, menedżera w tle lub Activity, który wywołuje bindService() w onStart() bez pasującego unbindService() w onStop(), powoduje wyciek ServiceConnection.
Dopóki to powiązanie jest aktywne, proces docelowy dziedziczy podwyższony priorytet klienta. Jeśli klient jest trwałym komponentem systemu lub aplikacją działającą na pierwszym planie, proces powiązanej usługi pozostaje przypięty w stanie BFGS lub BTOP na czas nieokreślony (w procstats wyświetla się wartość bliska 100%), co uniemożliwia LMK odzyskanie pamięci, nawet gdy usługa jest całkowicie nieaktywna.
Rozwiązanie: ogranicz zakres powiązań do cyklu życia komponentu, który ich potrzebuje, lub zaimplementuj limit czasu bezczynności, który po okresie braku aktywności wywołuje funkcję unbindService(). Unikaj odłączania i ponownego łączenia przy każdym wywołaniu RPC, ponieważ powoduje to nadmierne obciążenie procesora i powtarzające się obciążenie związane z konfiguracją usługi Binder. Zamiast tego łącz serie zadań za pomocą krótkiego timera bezczynności (np. od 5 do 30 sekund).
Umieszczanie powiązanej usługi w pobliżu interfejsu wymagającego dużej ilości pamięci
Domyślnie wszystkie komponenty w pliku APK działają w tym samym procesie. Jeśli aplikacja udostępnia lekką usługę powiązaną (np. NotificationListenerService, dostawcę widżetów lub usługę wtyczki, z którą system lub program uruchamiający nawiązuje powiązanie) w tym samym procesie co główny Activity, cały proces dziedziczy podwyższony stan usługi (BFGS lub PERC, zwykle oom_score_adj z 200 lub mniej).
Gdy użytkownik otworzy interfejs aplikacji, proces przydzieli duże hierarchie widoków, zdekodowane bitmapy i bufory graficzne. Gdy użytkownik opuści aplikację, proces nie przechodzi do poziomu CACHED (oom_score_adj – 900 lub wyższy), ponieważ aktywne powiązanie usługi utrzymuje proces na wyższym poziomie. Powoduje to 2 problemy:
- Brak kompresji pamięci w tle: system
CachedAppOptimizerkompresuje procesy tylko po przejściu w stanCACHED. - Brak przycinania pamięci podręcznej LRU i odzyskiwania pamięci przez LMK: system nie wysyła wywołań zwrotnych przycinania w tle powiązanych z listą LRU pamięci podręcznej (
TRIM_MEMORY_BACKGROUNDi wyżej), gdy powiązanie usługi utrzymuje proces w stanie podwyższonym. Jeśli aplikacja nie zwalnia jawnie zasobów interfejsu naTRIM_MEMORY_UI_HIDDENlubActivity.onStop(), szczytowe przydziały interfejsu pozostają w pamięci RAM z uprzywilejowanym wynikiem OOM, którego LMK nie może łatwo odzyskać.
Rozwiązanie: usuwaj z pamięci podręcznej interfejsu, zdekodowanych map bitowych i odwołań do widoków, gdy interfejs przestaje być widoczny, używając Activity.onStop(), ComponentCallbacks2.onTrimMemory(TRIM_MEMORY_UI_HIDDEN), Application.ActivityLifecycleCallbacks lub ProcessLifecycleOwner. Możesz też przenieść zawsze powiązaną usługę do osobnego, lekkiego procesu za pomocą atrybutu manifestu android:process, aby system mógł przenieść główny proces interfejsu do stanu CACHED w celu skompaktowania lub odzyskania pamięci niezależnie od innych procesów.
Pomijanie flag zrzeczenia się priorytetu
Wywołanie funkcji bindService() za pomocą funkcji BIND_AUTO_CREATE powoduje przeniesienie pełnego harmonogramu i priorytetu pamięci dzwoniącego do usługi docelowej. Gdy aplikacja działająca na pierwszym planie wiąże się z usługą działającą w tle, która służy do analiz, rejestrowania lub wstępnego pobierania danych, używając tylko BIND_AUTO_CREATE, nieumyślnie podnosi jej priorytet do BTOP.
Rozwiązanie: podczas wiązania z usługami pomocniczymi lub usługami o najwyższym priorytecie, które nie wymagają takiego samego poziomu ochrony jak interfejs użytkownika na pierwszym planie, połącz BIND_AUTO_CREATE z BIND_WAIVE_PRIORITY, BIND_NOT_FOREGROUND lub BIND_NOT_PERCEPTIBLE, aby system nadal mógł zarządzać procesem docelowym na liście LRU w pamięci podręcznej.
← Miejscowość | ↑ W górę | W całym systemie →