Функция заморозки кешированных приложений поддерживается в Android 11 (уровень API 30) и более поздних версиях. Эта функция приостанавливает работу кешированных процессов и снижает потребление ресурсов приложениями, которые могут пытаться работать в кеше.
Заморозка приложений в кеше позволяет хранить приложения в ОЗУ, не задействуя процессор. Если Android определяет, что приложение не должно работать, но может понадобиться в будущем, то его процесс замораживается, а не завершается. Это предотвращает холодный запуск, когда приложение снова понадобится.
Android замораживает кешированные приложения, перенося их процессы в замороженную группу cgroup. Это позволяет снизить потребление ресурсов ЦП в активном и бездействующем состоянии при наличии активных кешированных приложений. Вы можете включить заморозку приложений с помощью флага конфигурации системы или параметра для разработчиков.
В Android 14 (уровень API 34) и более поздних версиях заморозка кешированных приложений включает следующие надежные функции:
- Процессы приложений в кешированном состоянии замораживаются через 10 секунд после перехода в это состояние.
- Система немедленно размораживает процесс замороженного приложения во время события жизненного цикла. К таким событиям относятся получение намерения, запуск сервиса заданий или возобновление пользователем действия.
ActivityManagerService управляет всеми процессами приложений и принимает решения об их жизненном цикле. CachedAppOptimizer отвечает за заморозку процесса приложения.
Когда процесс приложения заморожен, все его потоки приостановлены и не могут выполнять задачи ЦП, пока не будут разморожены. В результате приложение не может выполнять сборку мусора и реагировать на события сокращения памяти. Дополнительную информацию вы найдете здесь: ComponentCallbacks2.onTrimMemory(int). Чтобы учесть это, начиная с Android 14:
- Приложения с видимым экземпляром
Activityполучают уведомлениеTRIM_MEMORY_UI_HIDDEN, как только переходят в фоновый режим. Приложения, которые остаются в жизненном цикле без интерфейса, например приложения с активной службой, могут получатьTRIM_MEMORY_BACKGROUND. Другие события обрезки не доставляются, поскольку, когда приложения соответствуют требованиям для этих событий, они должны быть заморожены. - Вскоре после перехода в кешированное состояние система может запросить у среды выполнения приложения выполнить сборку мусора, чтобы подготовиться к возможному замораживанию.
- Когда процесс приложения заморожен, могут выполняться дополнительные шаги по сжатию памяти, например запись грязных страниц в резервное хранилище и перенос анонимных страниц в ZRAM.
- Если все процессы определенного приложения заморожены, система закрывает все активные TCP-сокеты, поддерживаемые приложением. Это предотвращает отправку сервером TCP-пингов keepalive, которые могут разбудить модем устройства.
Замороженные процессы приложений, хранящихся в кеше, размораживаются, когда их состояние меняется с cached на более важное. Чтобы уменьшить количество событий разморозки в Android 14 и более поздних версиях, система ставит в очередь зарегистрированные в контексте трансляции, пока приложение находится в кешированном состоянии. Приемники, зарегистрированные в контексте, – это приемники, которые приложение регистрирует динамически, вызывая Context.registerReceiver. Система доставляет эти сообщения только после того, как приложение разморожено. В отличие от этого, система не ставит в очередь трансляции, объявленные в манифесте.
Заявленные в манифесте трансляции – это получатели, статически объявленные в AndroidManifest.xml с помощью элемента <receiver>. Система сразу же размораживает кешированное приложение, чтобы передать объявленные в манифесте трансляции.
Влияние на состояние системы
Если в кеше больше MAX_CACHED_PROCESSES процессов приложений, Android завершает процесс приложения, которое использовалось реже всего. На поддерживаемых устройствах с Android 14 или более поздней версией ОС значение MAX_CACHED_PROCESSES значительно увеличено, что позволяет хранить в ОЗУ гораздо больше кэшированных процессов приложений.
Если в ОЗУ будет храниться больше приложений, количество холодных запусков может сократиться на 30 %. Чем больше ОЗУ, тем сильнее будет эффект. При этом потребление ресурсов ЦП приложениями, находящимися в кеше, сводится к минимуму, что позволяет значительно экономить заряд батареи.
Исключения для морозильных камер
При определенных условиях процесс приложения может перейти в кешированное состояние, но остаться незамороженным. Эти исключения являются особенностями реализации и могут измениться в будущих версиях Android:
- Блокировка файлов. Если кешированный процесс удерживает блокировку файла, которая мешает другим некешированным процессам, то процесс, удерживающий блокировку, не замораживается.
BIND_WAIVE_PRIORITY-подключения. Процессы приложений с входящими подключениями, созданными с помощьюContext.BIND_WAIVE_PRIORITY, могут перейти в кешированное состояние, но останутся размороженными, пока все подключенные клиентские процессы также не будут кешированы. Это исключение поддерживает многопроцессные приложения, например веб-браузеры, использующие специальные вкладки.
Как реализовать заморозку приложений
Заморозка кешированных приложений использует заморозку cgroup v2 ядра. Его можно включить на устройствах с совместимым ядром. Включите параметр для разработчиков Приостанавливать выполнение кешированных приложений или установите для флага конфигурации устройства activity_manager_native_boot use_freezer значение true. Пример:
adb shell device_config put activity_manager_native_boot use_freezer true && adb rebootФункция заморозки отключается, когда вы устанавливаете флаг use_freezer в значение false или отключаете параметр для разработчиков. Пример:
adb shell device_config put activity_manager_native_boot use_freezer false && adb rebootВы можете изменить этот параметр, изменив конфигурацию устройства в выпуске или обновлении ПО.
Чтобы переопределить значение параметра MAX_CACHED_PROCESSES, например задать значение 1024 для тестирования, выполните следующие действия:
adb shell device_config put activity_manager max_cached_processes 1024adb shell device_config set_sync_disabled_for_tests persistent
Чтобы отменить переопределение MAX_CACHED_PROCESSES:
adb shell device_config delete activity_manager max_cached_processesadb shell device_config set_sync_disabled_for_tests none
Начиная с Android 16 (уровень API 36), в Android доступны официальные общедоступные API, такие как IBinder.FrozenStateChangeCallback и IBinder.addFrozenStateChangeCallback, которые позволяют отслеживать, когда удаленные процессы замораживаются или размораживаются. Компоненты, взаимодействующие с приложениями, которые могут быть помещены в кеш, могут использовать эти API для отслеживания замороженного состояния удаленных процессов.
Требования к устройству и ядру
Для заморозки кешированных приложений требуется поддержка cgroup версии 2 в ядре. Кроме того, для уведомлений об изменении состояния заморозки с помощью IBinder.FrozenStateChangeCallback требуется поддержка драйвера связывателя ядра, который является стандартным в общих ядрах Android (ACK) и общих образах ядра (GKI), начиная с Android 14 (уровень API 34) и выше.
Чтобы проверить, поддерживает ли устройство эти возможности, используйте стандартные команды adb:
Проверьте, включен ли замораживатель на устройстве (для пользовательских или отладочных сборок):
adb shell device_config get activity_manager_native_boot use_freezerИли убедитесь, что система активно замораживает процессы:
adb shell dumpsys activity | grep -A 20 "Apps frozen:"Как проверить поддержку контроллера заморозки cgroup v2 (для любого устройства)
Убедитесь, что
freezerесть в списке доступных контроллеров cgroup v2:adb shell cat /sys/fs/cgroup/cgroup.controllersНа устройстве с root-доступом или сборке userdebug можно проверить, смонтирован ли узел freezer cgroup v2 в дочерней группе cgroup:
adb root && adb shell ls /sys/fs/cgroup/uid_0/cgroup.freezeЕсли этот файл существует, ядро поддерживает cgroup v2 freezer.
Как проверить поддержку уведомлений об изменении состояния заморозки (для любого устройства)
На устройствах с Android 14 и более поздними версиями, на которых установлены совместимые драйверы связывателя GKI,
IBinder.addFrozenStateChangeCallbackуспешно регистрирует обратные вызовы. Если базовый драйвер связывания ядра не поддерживает уведомления о заморозке, метод вызывает исключениеUnsupportedOperationException.
Как обрабатывать специальные функции
Обычно процессы приложений не выполняют никаких действий, когда находятся в кеше, но некоторые приложения могут иметь специальные функции, поддерживаемые процессами, которые должны выполняться, пока приложение находится в кеше. Если на устройстве с такими приложениями включена заморозка приложений, кешированные процессы замораживаются, что может помешать работе специальных функций.
В качестве временного решения можно изменить статус процесса на "некешированный" до того, как процесс начнет выполнять какие-либо действия. Это позволит приложениям оставаться активными. Примеры активных статусов: связанная активная служба или активный статус.
Распространенные ошибки
Если процессы приложения заморожены, неправильная межпроцессная коммуникация (IPC) или планирование задач может привести к завершению работы приложения или его неожиданному поведению.
Синхронные транзакции связывания с замороженными процессами
Когда процесс клиентского приложения отправляет синхронную транзакцию Binder в процесс серверного приложения, который заморожен, система немедленно завершает процесс серверного приложения. Это предотвращает бесконечное блокирование клиентского потока при ожидании ответа от зависшего сервера. После этого клиентский поток получает RemoteException, и запускаются все зарегистрированные прослушиватели. Дополнительную информацию вы найдете в статье IBinder.linkToDeath.
Основная причина. Обычно эта ошибка возникает из-за неполадки в клиентском приложении. Когда клиент подключается к сервису, серверный процесс привязывается к клиенту и не может перейти в кешированное состояние раньше клиента. Дополнительную информацию вы найдете здесь: Context.bindService. Однако после того, как клиент вызывает Context.unbindService, процесс сервера может быть закеширован и заморожен. Если клиент продолжит использовать кешированную ссылку IBinder после отмены привязки, он рискует связаться с замороженным процессом.
Чтобы избежать этой проблемы, убедитесь, что клиентские приложения удаляют ссылки IBinder сразу после вызова Context.unbindService.
Управление удаленными обратными вызовами для клиентских процессов
Сервисы и системные компоненты, которые поддерживают длительные обратные вызовы Binder к клиентским процессам, могут предотвращать синхронные сбои и асинхронные переполнения буфера, отслеживая состояние заморозки клиента:
- Зарегистрируйте прослушиватель изменения состояния. Используйте
IBinder.addFrozenStateChangeCallbackдля входящих токенов связывателя клиента, чтобы получать уведомления, когда процесс клиента входит в заморозку или выходит из нее. - Приостановить отправку при заморозке. Когда клиент переходит в состояние
STATE_FROZEN, приостанавливается отправка обратных вызовов или обновлений состояния этому клиенту. - Возобновление работы и отправка данных после разморозки. Когда клиент переходит в состояние
STATE_UNFROZEN, возобновите отправку обратных вызовов и передайте все необходимые пакетные или консолидированные обновления. - Используйте RemoteCallbackList. Системные службы, использующие
RemoteCallbackList, могут настроить замороженные правила вызываемого объекта, чтобы автоматически приостанавливать и возобновлять отправку обратного вызова без необходимости поддерживать логику отслеживания вручную. Подробнее о рекомендациях по использованию Binder Freezer для системных сервисов…
Переполнение буфера транзакций асинхронного связывателя
Когда процесс серверного приложения получает асинхронные (oneway) транзакции Binder во время заморозки, они буферизуются в буфере для каждого процесса. Если сервер получает слишком много асинхронных транзакций, пока он заморожен, буфер переполняется и система завершает процесс серверного приложения.
Чтобы избежать переполнения буфера, не отправляйте слишком много асинхронных транзакций binder в процессы, которые могут быть сохранены в кеше или заморожены.
Повторное выполнение запланированных задач после разморозки
Если приложение выполняет повторяющиеся задачи, они приостанавливаются, пока процесс заморожен. Подробнее: ScheduledThreadPoolExecutor.scheduleAtFixedRate, Timer.scheduleAtFixedRate. Когда процесс размораживается, накопившиеся пропущенные выполнения могут запускаться быстро друг за другом практически без задержки.
Чтобы избежать резкого увеличения количества выполнений при разморозке приложения, используйте для фоновых задач scheduleWithFixedDelay вместо scheduleAtFixedRate. Вы также можете использовать сочетание клавиш WorkManager.
Тестирование и устранение неполадок с заморозкой приложений
Чтобы проверить, правильно ли работает заморозка приложений, или устранить связанные с ней неполадки, используйте следующие диагностические инструменты и команды:
Команды для менеджера действий
Вы можете использовать команды adb shell am, чтобы вручную управлять заморозкой и сжатием для определенного процесса:
Чтобы принудительно заморозить процесс, выполните следующие действия:
adb shell am freeze <process>Как принудительно разморозить процесс
adb shell am unfreeze <process>Чтобы принудительно выполнить полную дефрагментацию памяти для процесса:
adb shell am compact full <process>
Проверка logcat
Чтобы посмотреть записи о заморозке и разморозке процессов, когда они перемещаются в морозильник или из него, используйте logcat:
adb logcat | grep -i "\(freezing\|froze\)"В журналах причин разблокировки перечисляются перечисляемые значения из перечисления буфера протокола UnfreezeReason.
Проверка dumpsys
Чтобы посмотреть список замороженных процессов, используйте dumpsys activity:
adb shell dumpsys activity | grep -A 20 "Apps frozen:"Проверьте, есть ли файл /sys/fs/cgroup/uid_0/cgroup.freeze.
ApplicationExitInfo
Чтобы узнать причину завершения предыдущего процесса, ознакомьтесь с информацией на странице ActivityManager.getHistoricalProcessExitReasons.
Если процесс приложения был завершен из-за проблемы, связанной с заморозкой, например из-за получения синхронной транзакции binder в замороженном состоянии, причиной выхода будет ApplicationExitInfo.REASON_FREEZER.
Трассировка Perfetto
События, связанные с заморозкой, передаются на дорожку с названием Freezer в процессе system_server в трассировках Perfetto:
- Срезы
FreezeиUnfreezeпоказывают, когда процесс меняет состояние. - События
updateAppFreezeStateLSPпоказывают, когда системный сервер повторно проверяет атрибуты процесса, чтобы принять решение о заморозке или разморозке.
Вы можете просматривать эти события непосредственно в интерфейсе Perfetto или анализировать их с помощью 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 %");
В стандартной библиотеке PerfettoSQL события заморозки также обобщены в таблице android_freezer_events.