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 Android używa stanów procesów , aby śledzić znaczenie 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 Low Memory Killer (LMK).
Typowe stany procesów
W tabeli poniżej znajdziesz niektóre z najczęstszych stanów procesów i ich typowe wartości oom_score_adj. Pełną i aktualną listę znajdziesz w kodzie źródłowym Androida w plikach android.app.ActivityManager i com.android.server.am.psc.Constants.
| Stan procesu (skrót) | Opis | Typowa wartość oom_score_adj |
|---|---|---|
| PER (Persistent) | Procesy systemowe, które muszą zawsze działać (np. Telephony). | -800 |
| TOP | Proces, z którym użytkownik obecnie wchodzi w interakcję. | 0 |
| VIS (Visible) | Proces ma widoczną aktywność (np. za półprzezroczystym oknem). | 100 |
| PERC (Perceptible) | Proces działający w tle, o którym użytkownik wie (np. odtwarzanie muzyki). | 200 |
| FGS | Proces hostujący usługę działającą na pierwszym planie. | Od 0 do 200 (zmienna) |
| BTOP (Bound Top) | Proces powiązany z aplikacją TOP. | 100 |
| BFGS | Powiązana usługa działająca na pierwszym planie (zwykle powiązana z systemem). | 0 |
| PREV (Previous) | Ostatni proces, w którym użytkownik był przed bieżącym. | 700 |
| CACHED | Aplikacje działające w tle, które można bezpiecznie zakończyć. | Od 900 do 999 |
Wpływ powiązań usług
Gdy proces klienta (np. aplikacja w stanie TOP) wiąże się z usługą w procesie serwera, proces serwera często dziedziczy podwyższony 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 Context.BIND_AUTO_CREATE.
Deweloperzy mogą jednak kontrolować, jak powiązanie wpływa na znaczenie procesu docelowego, używając różnych flag w bindService().
Kluczowe flagi BIND dla wyniku OOM
Te flagi są najbardziej istotne podczas zarządzania obciążeniem pamięci w całym systemie:
BIND_AUTO_CREATE: najczęściej używana flaga. Zapewnia, że proces usługi jest uruchamiany i utrzymywany przy życiu tak długo, jak istnieje powiązanie. Domyślnie podnosi też priorytet procesu serwera, aby był zgodny z priorytetem klienta.BIND_NOT_FOREGROUND: uniemożliwia podniesienie procesu usługi docelowej do priorytetu planowania na pierwszym planie (priorytetu procesora). Nadal jednak umożliwia podniesienie priorytetu pamięci (oom_score_adj). Jest to przydatne w przypadku pracy w tle, która nie powinna konkurować z interfejsem użytkownika o cykle procesora, ale powinna być chroniona przed zakończeniem.BIND_WAIVE_PRIORITY: bardzo silna flaga, która nakazuje systemowi nie wpływać na priorytet planowania ani zarządzania pamięcią procesu docelowego. Proces usługi będzie zarządzany tak, jakby był zwykłym procesem działającym 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: wskazuje, że usługa jest ważniejsza niż sama aplikacja klienta. Gdy system będzie musiał odzyskać pamięć, najpierw zakończy aplikację klienta, a potem powiązaną usługę. Jest to „silniejsza” flaga niżBIND_AUTO_CREATE, ponieważ zapewnia dodatkową warstwę ochrony usługi kosztem klienta.BIND_NOT_PERCEPTIBLE: obniża znaczenie usługi docelowej poniżejPERCEPTIBLEpoziomu, co pozwala systemowi odzyskać jej pamięć, aby zrobić miejsce na ważniejsze procesy widoczne dla użytkownika.
Ćwiczenie: obserwowanie efektów powiązania
Aby pokazać, jak powiązanie z aplikacją TOP wpływa na stan oddzielnego procesu, użyjemy aplikacji MemoryLab.
1. Uruchom MemoryLab
Aplikację uruchamia to polecenie. Po otwarciu upewnij się, że aplikacja pozostaje na pierwszym planie (nie naciskaj jeszcze przycisku Home ani nie przełączaj aplikacji).
adb shell am start -n com.android.memorylab/.MainActivity
2. Określ procesy
Przed powiązaniem sprawdź stany procesów. MemoryLab uruchamia swój 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 danych wyjściowych:
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 w stanie TOP. Proces :remote nie jest jeszcze uruchomiony.
3. Aktywuj powiązanie
Aby aktywować powiązanie usługi, wyślij do aplikacji transmisję:
adb shell am broadcast -a com.android.memorylab.LEAK_BINDER
4. Obserwuj podwyższony stan
Ponownie sprawdź stany procesów:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
Przykładowy fragment danych wyjściowych:
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 w stanie BTOP (Bound TOP) z wartością oom_score_adj równą 100. Jest on znacznie lepiej chroniony niż typowa usługa działająca w tle (która miałaby wartość 500 lub wyższą). Zapis
<=Proc{...} pokazuje, który proces odpowiada za to podniesienie priorytetu.
5. Przejdź do tła
Naciśnij przycisk HOME na urządzeniu. Ponownie sprawdź stany:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
Przykładowy fragment danych wyjściowych:
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
Teraz oba procesy przeszły do stanu o niższym priorytecie (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ą procstats
Narzędzie procstats umożliwia wyświetlanie historii tych stanów.
# View stats for MemoryLab over the last hour
adb shell dumpsys procstats --hours 1 com.android.memorylab
Przykładowy fragment danych wyjściowych:
* 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%
W tym przypadku Bnd Top wskazuje procent czasu, przez jaki proces zdalny był powiązany z aplikacją w stanie TOP.
Przechwytywanie i analizowanie powiązań za pomocą Perfetto
Narzędzie dumpsys umożliwia uzyskanie migawki, a Perfetto pozwala zobaczyć dokładny moment wystąpienia powiązania i sposób zmiany wyniku OOM w czasie rzeczywistym.
1. Zarejestruj ślad
Użyj konfiguracji, która obejmuje linux.process_stats i kategorię atrace am:
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. Wysyłaj zapytania o przejścia wyniku OOM
Za pomocą PerfettoSQL możesz sprawdzić, jak zmienił się wynik OOM procesu zdalnego w stosunku do procesu interfejsu użytkownika:
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. Określ zdarzenia powiązania
Aby zobaczyć, kiedy dokładnie powstała zależność powiązania 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 aplikacjami
Sam system Android często wiąże 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 przy życiu i w pamięci, system unika kosztownego obciążenia związanego z uruchomieniem „na zimno” (wczytaniem pliku APK, zainicjowaniem środowiska wykonawczego i utworzeniem obiektu aplikacji), gdy nastąpi krytyczna interakcja z użytkownikiem. Istnieją też inne powiązania, które zapobiegają częstym zimnym startom aplikacji, które muszą obsługiwać strumienie zdarzeń w tle.
Oto kilka przykładów z życia codziennego, które możesz zaobserwować na typowym urządzeniu:
VoiceInteractor
Użytkownicy oczekują, że asystent cyfrowy będzie wbudowany w system operacyjny telefonu, że będzie można go natychmiast przywołać za pomocą wypowiedzianego hasła lub szybkiego gestu, a interakcja będzie płynna i bezproblemowa.
Gdy nastąpi aktywacja asystenta (np. hasło „OK Google” na telefonach Google Pixel), 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 (Bound Foreground Service), 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,
Dobrym przykładem jest usługa, która odbiera wywołania 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 do stanu buforowania i może zostać zakończony przez LMK.
Gdy nadejdzie kolejne powiadomienie – być może kilka sekund później – system będzie musiał ponownie uruchomić proces aplikacji, aby dostarczyć zdarzenie. Ten ciągły cykl kończenia i zimnego startu zużywałby znacznie więcej procesora i baterii niż po prostu utrzymywanie procesu powiązanego i działającego w tle.
Ekran „-1” programu uruchamiającego (kanał informacyjny)
Nowoczesne aplikacje uruchamiające 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 programu uruchamiającego i płynnie zintegrowany z interfejsem programu uruchamiającego. Kanał wiadomości może być udostępniany przez inną aplikację. Na przykład na telefonach Google Pixel program uruchamiający integruje się z kanałem udostępnianym przez aplikację Google.
Gdy przesuniesz palcem w lewo na ekranie głównym, aby wyświetlić kanał wiadomości, przejście musi być płynne. Program uruchamiający 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 działa program uruchamiający. Dzięki temu zawartość kanału jest renderowana i gotowa w pamięci nawet wtedy, gdy jej nie widzisz.
Inne typowe przykłady
- Program uruchamiający (HOME_APP_ADJ): aplikacja uruchamiająca (Home) ma własne specjalne
miejsce na liście priorytetów. Chociaż nie zawsze jest powiązana z usługą, ma przypisaną wartość
HOME_APP_ADJ(zwykle 600). System woli utrzymywać program uruchamiający przy życiu, ponieważ użytkownik często do niego wraca. W rzeczywistości system woli zakończyć wcześniej używaną aplikację (PREV_APP_ADJ = 700) niż program uruchamiający, ponieważ zakończenie programu uruchamiającego spowoduje spowolnienie działania aplikacji podczas wychodzenia z niej, ponieważ użytkownik będzie musiał poczekać na zimny start programu uruchamiającego. - Edytor metody wprowadzania (IME): podczas pisania system wiąże się z wybraną aplikacją klawiatury (np. Gboard). Dzięki temu proces klawiatury pozostaje w podwyższonym stanie, nawet jeśli klawiatura jest tymczasowo ukryta. Dzięki temu klawiatura może się natychmiast pojawić, gdy dotkniesz innego pola tekstowego.
- Płatności NFC: gdy dotkniesz telefonem, aby zapłacić, system wiąże się z usługą płatności NFC (np. Portfel Google). Te transakcje często mają ścisłe wymagania dotyczące czasu rzeczywistego ze strony terminala sprzedawcy. Jeśli aplikacja płatnicza musiała się uruchomić na zimno, transakcja mogła się nie udać.
Kompromisy i spadek wydajności
Powiązania są niezbędne do zapewnienia wydajności i poprawności, ale mają wpływ na kondycję pamięci systemu.
- Mniejsza elastyczność: każdy powiązany proces to proces, który LMK nie może łatwo zakończyć. Zmniejsza to „bufor” procesów buforowanych, których system może użyć do zwolnienia pamięci pod obciążeniem.
- Pogorszenie spadku wydajności: jeśli zbyt wiele procesów jest powiązanych, system może się znaleźć w sytuacji, w której prawie nie ma procesów działających 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 zakończenia ważniejszych procesów lub do intensywnego korzystania z pamięci podręcznej strony.
← Lokalność | ↑ W górę | W całym systemie →