Pamięć podręczna aplikacji

Android 11 (poziom 30 interfejsu API) lub nowszy obsługuje zamrażanie aplikacji w pamięci podręcznej. Ta funkcja zatrzymuje wykonywanie procesów w pamięci podręcznej i ogranicza wykorzystanie zasobów przez nieprawidłowo działające aplikacje, które mogą próbować działać w pamięci podręcznej.

Zamrażarka aplikacji w pamięci podręcznej utrzymuje aplikacje w pamięci RAM, ale nie obciąża procesora. Jeśli Android uzna, że aplikacja nie powinna wykonywać żadnych działań, ale może być potrzebna w przyszłości, zamraża jej proces, zamiast go kończyć. Zapobiega to uruchomieniu „na zimno”, gdy aplikacja jest ponownie potrzebna.

Android zamraża aplikacje w pamięci podręcznej, przenosząc ich procesy do zamrożonej grupy cgroup. Zmniejsza to zużycie procesora w stanie aktywnym i bezczynnym w przypadku aktywnych aplikacji w pamięci podręcznej. Możesz włączyć zamrażanie aplikacji za pomocą flagi konfiguracji systemu lub opcji programisty.

W Androidzie 14 (API na poziomie 34) i nowszym zamrażarka aplikacji w pamięci podręcznej ma te zaawansowane funkcje:

  • Procesy aplikacji w stanie buforowanym są zamrażane 10 sekund po przejściu w ten stan.
  • System natychmiast odblokowuje zamrożony proces aplikacji podczas zdarzenia związanego z cyklem życia. Obejmują one otrzymanie intencji, rozpoczęcie usługi zadania lub wznowienie aktywności przez użytkownika.

ActivityManagerService zarządza wszystkimi procesami aplikacji i podejmuje decyzje dotyczące cyklu życia aplikacji. CachedAppOptimizer odpowiada za zamrażanie procesu aplikacji.

Gdy proces aplikacji jest zamrożony, wszystkie jego wątki są zawieszone i nie mogą wykonywać pracy procesora, dopóki nie zostaną odmrożone. W związku z tym aplikacja nie może przeprowadzać odzyskiwania pamięci ani odpowiadać na zdarzenia związane z przycinaniem pamięci. Więcej informacji znajdziesz w artykule ComponentCallbacks2.onTrimMemory(int). Aby to uwzględnić, od Androida 14:

  • Aplikacje z widoczną instancją Activity są powiadamiane o TRIM_MEMORY_UI_HIDDEN, gdy tylko przejdą w tle. Aplikacje, które pozostają w cyklu życia bez interfejsu, np. aplikacje z usługą działającą na pierwszym planie, mogą otrzymać TRIM_MEMORY_BACKGROUND. Inne zdarzenia przycinania nie są dostarczane, ponieważ gdy aplikacje kwalifikują się do tych zdarzeń, oczekuje się, że zostaną zamrożone.
  • Krótko po przejściu w stan buforowania system może poprosić środowisko wykonawcze aplikacji o przeprowadzenie odśmiecania pamięci w ramach przygotowań do potencjalnego zamrożenia.
  • Gdy proces aplikacji jest zamrożony, mogą wystąpić dodatkowe kroki kompresji pamięci, takie jak zapisywanie zmodyfikowanych stron w pamięci zapasowej i przenoszenie anonimowych stron do zRAM.
  • Jeśli wszystkie procesy danej aplikacji są zamrożone, system zamyka wszystkie aktywne gniazda TCP utrzymywane przez aplikację. Zapobiega to wysyłaniu przez serwer pingów TCP keepalive, które mogłyby wybudzić modem urządzenia.

Procesy aplikacji w pamięci podręcznej są odblokowywane, gdy ich stan zmienia się z „cached” na stan o większym znaczeniu. Aby ograniczyć liczbę zdarzeń odblokowania w Androidzie 14 i nowszych wersjach, system umieszcza w kolejce transmisje zarejestrowane w kontekście, gdy aplikacja jest w stanie buforowania. Rejestrowane w kontekście transmisje to odbiorniki, które aplikacja rejestruje dynamicznie, wywołując metodę Context.registerReceiver. System dostarcza te kolejkowane transmisje dopiero po odblokowaniu aplikacji. W przeciwieństwie do tego system nie umieszcza w kolejce transmisji zadeklarowanych w pliku manifestu. Nadawanie komunikatów zadeklarowanych w pliku manifestu to odbiorniki zadeklarowane statycznie w AndroidManifest.xml za pomocą elementu <receiver>. System natychmiast odblokowuje aplikację zapisaną w pamięci podręcznej, aby dostarczyć transmisje zadeklarowane w pliku manifestu.

Wpływ na stan systemu

Jeśli liczba procesów aplikacji w pamięci podręcznej przekracza MAX_CACHED_PROCESSES, Android zamyka proces aplikacji w pamięci podręcznej, który był najrzadziej używany. Na obsługiwanych urządzeniach z Androidem 14 lub nowszym wartość MAX_CACHED_PROCESSES jest znacznie zwiększona, co pozwala urządzeniom przechowywać w pamięci RAM znacznie więcej procesów aplikacji w pamięci podręcznej.

Przechowywanie większej liczby aplikacji w pamięci RAM w postaci pamięci podręcznej pozwala zmniejszyć liczbę uruchomień „na zimno” nawet o 30%, a wartość ta zależy od całkowitej pamięci RAM urządzenia. Jednocześnie zużycie procesora przez aplikacje w pamięci podręcznej jest minimalizowane, co pozwala znacznie oszczędzać baterię.

Wyjątki dotyczące zamrażarek

W określonych warunkach proces aplikacji może przejść w stan buforowania, ale pozostać w stanie odblokowanym. Te wyjątki są szczegółami implementacji i mogą się zmienić w przyszłych wersjach Androida:

  • Blokady plików: jeśli proces w pamięci podręcznej ma blokadę pliku, która blokuje inne procesy spoza pamięci podręcznej, proces z blokadą nie jest zamrażany.
  • BIND_WAIVE_PRIORITY powiązania: procesy aplikacji z powiązaniami przychodzącymi utworzonymi za pomocą Context.BIND_WAIVE_PRIORITY mogą przejść w stan buforowany, ale pozostaną niezamrożone, dopóki wszystkie połączone procesy klienta również nie zostaną buforowane. To wyłączenie obsługuje aplikacje wieloprocesowe, takie jak przeglądarki internetowe korzystające z niestandardowych kart.

Wdrażanie zamrażarki aplikacji

Zamrażarka aplikacji w pamięci podręcznej korzysta z zamrażarki cgroup v2 jądra. Można ją włączyć na urządzeniach dostarczanych ze zgodnym jądrem. Włącz opcję dla programistów Zawieszanie wykonywania w przypadku aplikacji w pamięci podręcznej lub ustaw flagę konfiguracji urządzenia activity_manager_native_boot use_freezer na true. Przykład:

adb shell device_config put activity_manager_native_boot use_freezer true && adb reboot

Zamrażanie jest wyłączone, gdy ustawisz flagę use_freezer na false lub wyłączysz opcję dla programistów. Przykład:

adb shell device_config put activity_manager_native_boot use_freezer false && adb reboot

To ustawienie możesz włączyć lub wyłączyć, zmieniając konfigurację urządzenia w wersji lub aktualizacji oprogramowania.

Aby zastąpić wartość MAX_CACHED_PROCESSES, np. ustawić wartość 1024 na potrzeby testowania:

adb shell device_config put activity_manager max_cached_processes 1024
adb shell device_config set_sync_disabled_for_tests persistent

Aby cofnąć zastąpienie MAX_CACHED_PROCESSES:

adb shell device_config delete activity_manager max_cached_processes
adb shell device_config set_sync_disabled_for_tests none

Od Androida 16 (API na poziomie 36) Android udostępnia oficjalne publiczne interfejsy API, takie jak IBinder.FrozenStateChangeCallback i IBinder.addFrozenStateChangeCallback, które umożliwiają obserwowanie, kiedy procesy zdalne są zamrażane lub odmrażane. Komponenty wchodzące w interakcje z aplikacjami, które mogą zostać zapisane w pamięci podręcznej, mogą używać tych interfejsów API do śledzenia stanu zamrożenia procesów zdalnych.

Wymagania dotyczące urządzenia i jądra

Zamrażanie aplikacji w pamięci podręcznej wymaga obsługi cgroup w wersji 2. Dodatkowo powiadomienia o zmianie stanu zamrożenia za pomocą IBinder.FrozenStateChangeCallback wymagają obsługi sterownika Binder w jądrze, która jest standardem w przypadku wspólnych jąder Androida (ACK) i ogólnych obrazów jądra (GKI) od Androida 14 (API na poziomie 34) i nowszych wersji.

Możesz sprawdzić, czy urządzenie obsługuje te funkcje, za pomocą standardowych poleceń adb:

  • Sprawdź, czy zamrażarka jest włączona na urządzeniu (w przypadku wersji użytkownika lub wersji debugowania):

    adb shell device_config get activity_manager_native_boot use_freezer

    Możesz też sprawdzić, czy system aktywnie wstrzymuje procesy:

    adb shell dumpsys activity | grep -A 20 "Apps frozen:"
  • Sprawdź obsługę kontrolera zamrażania cgroup v2 (na dowolnym urządzeniu):

    Sprawdź, czy freezer znajduje się na liście dostępnych kontrolerów cgroup w wersji 2:

    adb shell cat /sys/fs/cgroup/cgroup.controllers

    Alternatywnie, na urządzeniu z dostępem do roota lub w wersji userdebug sprawdź, czy węzeł zamrażania cgroup w wersji 2 jest zamontowany w podrzędnej grupie cgroup:

    adb root && adb shell ls /sys/fs/cgroup/uid_0/cgroup.freeze

    Jeśli ten plik istnieje, jądro obsługuje zamrażanie cgroup w wersji 2.

  • Sprawdzanie obsługi powiadomień o zmianie stanu zamrożenia (na dowolnym urządzeniu):

    Na urządzeniach z Androidem 14 lub nowszym z kompatybilnymi sterownikami interfejsu GKI IBinder.addFrozenStateChangeCallback rejestruje wywołania zwrotne. Jeśli sterownik bindera jądra nie obsługuje powiadomień o zamrożeniu, metoda zgłasza wyjątek UnsupportedOperationException.

Obsługa funkcji niestandardowych

Procesy aplikacji nie powinny wykonywać żadnych działań, gdy są w pamięci podręcznej, ale niektóre aplikacje mogą mieć funkcje niestandardowe obsługiwane przez procesy, które powinny działać, gdy są w pamięci podręcznej. Gdy na urządzeniu z takimi aplikacjami włączona jest funkcja zamrażania aplikacji, procesy w pamięci podręcznej są zamrażane i mogą uniemożliwiać działanie funkcji niestandardowych.

Aby obejść ten problem, możesz zmienić stan procesu na niebuforowany, zanim proces będzie musiał wykonać jakąkolwiek pracę. Dzięki tej zmianie aplikacje pozostaną aktywne. Przykłady aktywnych stanów to powiązana usługa na pierwszym planie lub stan na pierwszym planie.

Typowe rodzaje błędów

Gdy procesy aplikacji są zamrożone, nieprawidłowa komunikacja międzyprocesowa (IPC) lub planowanie zadań może prowadzić do zakończenia działania aplikacji lub nieoczekiwanego zachowania.

Synchroniczne transakcje w usłudze Binder do zamrożonych procesów

Gdy proces aplikacji klienckiej wyśle synchroniczną transakcję powiązań do zamrożonego procesu aplikacji serwera, system natychmiast zakończy ten proces. Zapobiega to blokowaniu wątku klienta na czas nieokreślony podczas oczekiwania na odpowiedź z zamrożonego serwera. Wątek klienta otrzymuje wtedy RemoteException, a wszyscy zarejestrowani odbiorcy są wywoływani. Więcej informacji znajdziesz w sekcji IBinder.linkToDeath.

Przyczyna główna: ten błąd jest zwykle spowodowany błędem w aplikacji klienckiej. Gdy klient wiąże się z usługą, proces serwera jest powiązany z klientem i nie może przejść do stanu buforowanego przed klientem. Więcej informacji znajdziesz tutaj: Context.bindService. Gdy jednak klient wywoła Context.unbindService, proces serwera może zostać zapisany w pamięci podręcznej i zamrożony. Jeśli klient po odłączeniu nadal używa buforowanego odwołania IBinder, może komunikować się z zamrożonym procesem.

Aby uniknąć tego problemu, upewnij się, że aplikacje klienckie odrzucają odwołania IBinder natychmiast po wywołaniu funkcji Context.unbindService.

Zarządzanie zdalnymi wywołaniami zwrotnymi do procesów klienta

Usługi i komponenty systemowe, które utrzymują długotrwałe wywołania zwrotne Binder do procesów klienta, mogą zapobiegać synchronicznym awariom i asynchronicznym przepełnieniom bufora, śledząc stan zamrożenia klienta:

  • Zarejestruj odbiorcę zmiany stanu: użyj funkcji IBinder.addFrozenStateChangeCallback na przychodzących tokenach powiązań klienta, aby otrzymywać powiadomienia, gdy proces klienta wchodzi w stan zamrożenia lub z niego wychodzi.
  • Wstrzymaj wysyłanie podczas zamrażania: gdy klient przechodzi w stan STATE_FROZEN, wstrzymaj wysyłanie wywołań zwrotnych lub aktualizacji stanu do tego klienta.
  • Wznów i przekaż po odblokowaniu: gdy klient przejdzie na STATE_UNFROZEN, wznów wysyłanie wywołań zwrotnych i przekaż wszelkie niezbędne aktualizacje zbiorcze lub skonsolidowane.
  • Używanie RemoteCallbackList: usługi systemowe korzystające z RemoteCallbackList mogą konfigurować zasady zamrożonego wywoływanego, aby automatycznie wstrzymywać i wznawiać wysyłanie wywołań zwrotnych bez konieczności utrzymywania logiki śledzenia ręcznego. Więcej informacji znajdziesz w artykule Rekomendacje dotyczące zamrażania Binderów w przypadku usług systemowych.

Przepełnienie bufora transakcji asynchronicznej zszywarki

Gdy proces aplikacji serwera odbiera asynchroniczne transakcje interfejsu (oneway) podczas zamrożenia, transakcje są buforowane w buforze dla każdego procesu. Jeśli serwer otrzyma zbyt wiele transakcji asynchronicznych w czasie, gdy jest zamrożony, bufor przepełni się, a system zakończy proces aplikacji serwera.

Aby zapobiec przepełnieniu bufora, unikaj wysyłania zbyt wielu asynchronicznych transakcji Binder do procesów, które mogą być buforowane lub zamrożone.

Wielokrotne wykonywanie zaplanowanych zadań po odblokowaniu

Jeśli aplikacja wykonuje powtarzalne zadania, są one wstrzymywane, gdy proces jest zamrożony. Więcej informacji znajdziesz w sekcji ScheduledThreadPoolExecutor.scheduleAtFixedRate lub Timer.scheduleAtFixedRate. Gdy proces zostanie odblokowany, zgromadzone pominięte wykonania mogą być wykonywane szybko, jeden po drugim, praktycznie bez opóźnienia.

Aby zapobiec nagłemu wzrostowi liczby wykonań po odblokowaniu aplikacji, używaj scheduleWithFixedDelay zamiast scheduleAtFixedRate w przypadku zadań w tle. Możesz też użyć WorkManager.

Testowanie i rozwiązywanie problemów z zamrażarką aplikacji

Aby sprawdzić, czy zamrażarka aplikacji działa zgodnie z oczekiwaniami, lub rozwiązać problemy z nią związane, użyj tych narzędzi diagnostycznych i poleceń:

Polecenia menedżera aktywności

Możesz użyć poleceń adb shell am, aby ręcznie kontrolować zamrażanie i kompresję w przypadku konkretnego procesu:

  • Wymuszanie zamrożenia procesu:

    adb shell am freeze <process>
  • Wymuszanie odblokowania procesu:

    adb shell am unfreeze <process>
  • Wymuś pełną kompresję pamięci w procesie:

    adb shell am compact full <process>

Sprawdzanie logcat

Wyświetl logcat, aby zobaczyć zamrożone i odmrożone wpisy za każdym razem, gdy proces jest przenoszony do lub z zamrażarki:

adb logcat | grep -i "\(freezing\|froze\)"

Wartości wyjściowe logów przyczyny odblokowania wyliczone z wyliczenia bufora protokołu UnfreezeReason.

Sprawdzanie za pomocą narzędzia dumpsys

Sprawdź listę zamrożonych procesów za pomocą polecenia dumpsys activity:

adb shell dumpsys activity | grep -A 20 "Apps frozen:"

Sprawdź, czy plik /sys/fs/cgroup/uid_0/cgroup.freeze jest obecny.

ApplicationExitInfo

Aby wysłać zapytanie o powód wcześniejszego zakończenia procesu, zapoznaj się z tym artykułem:ActivityManager.getHistoricalProcessExitReasons Jeśli proces aplikacji został zakończony z powodu problemu związanego z zamrożeniem, np. otrzymania synchronicznej transakcji bindera podczas zamrożenia, jako przyczynę zakończenia ustawia się ApplicationExitInfo.REASON_FREEZER.

Śledzenie Perfetto

Zdarzenia związane z zamrażaniem są emitowane na ścieżkę o nazwie Freezer w ramach procesu system_server w śladach Perfetto:

  • Wycinki Freeze i Unfreeze wskazują, kiedy proces zmienia stan.
  • Zdarzenia updateAppFreezeStateLSP pokazują, kiedy serwer systemu ponownie sprawdza atrybuty procesu, aby podjąć decyzję o zamrożeniu lub odmrożeniu.

Możesz sprawdzić te zdarzenia bezpośrednio w interfejsie Perfetto lub przeanalizować je za pomocą PerfettoSQL:

INCLUDE PERFETTO MODULE slices.with_context;
SELECT *
FROM process_slice
WHERE process_name = "system_server"
AND track_name = "Freezer"
AND (name LIKE "Freeze %" OR name LIKE "Unfreeze %");

W standardowej bibliotece PerfettoSQL zdarzenia zamrażania są też podsumowywane w tabeli android_freezer_events.