Процессы приложений на Android не изолированы. Приложения часто полагаются на сервисы, предоставляемые другими приложениями или самой системой. Когда один процесс подключается к другому через привязку сервиса, создается зависимость, которая сильно влияет на то, как фреймворк Android управляет памятью.
Состояния процессов и оценки OOM
Фреймворк Android использует состояния процессов, чтобы отслеживать важность каждого запущенного процесса. Затем OomAdjuster использует эти состояния, чтобы назначить значение корректировки оценки OOM (oom_score_adj) в диапазоне от -1000 до 1000.
Чем ниже значение oom_score_adj, тем важнее процесс и тем меньше вероятность, что он будет завершен из-за нехватки памяти.
Распространенные состояния процессов
В таблице ниже приведены некоторые из наиболее распространенных состояний процесса и их типичные значения oom_score_adj. Полный и актуальный список можно найти в файлах android.app.ActivityManager и com.android.server.am.psc.Constants в исходном коде Android.
| Состояние процесса (аббр.) | Описание | Стандартный атрибут: oom_score_adj |
|---|---|---|
| PER (постоянный) | Системные процессы, которые должны выполняться всегда (например, телефония). | -800 |
| TOP | Процесс, с которым в данный момент взаимодействует пользователь. | 0 |
| VIS (видимый) | Процесс имеет видимое действие (например, за полупрозрачным диалоговым окном). | 100 |
| PERC (Perceptible) | Фоновый процесс, о котором пользователь знает (например, воспроизведение музыки). | 200 |
| FGS | Процесс, в котором размещена активная служба. | От 0 до 200 (может меняться) |
| BTOP (Bound Top) | Процесс, связанный с приложением TOP. | 100 |
| BFGS | Связанная активная служба (обычно связанная с системой). | 0 |
| PREV (назад) | Последний процесс, в котором пользователь находился до текущего. | 700 |
| CACHED | Фоновые приложения, которые можно безопасно закрыть. | 900–999 |
Влияние привязок сервисов
Когда клиентский процесс (например, приложение в состоянии TOP) связывается с сервисом в серверном процессе, серверный процесс часто наследует повышенный приоритет. Это гарантирует, что сервис будет доступен до тех пор, пока он нужен клиенту.

Управление наследованием с помощью флагов BIND
Наследование – это режим работы по умолчанию при использовании Context.BIND_AUTO_CREATE.
Однако разработчики могут управлять тем, как привязка влияет на важность целевого процесса, используя различные флаги в bindService().
Ключевые флаги BIND для оценки OOM
При управлении нагрузкой на память в масштабах всей системы наиболее актуальны следующие флаги:
BIND_AUTO_CREATE– наиболее распространенный флаг. Это гарантирует, что процесс службы будет запущен и будет поддерживаться до тех пор, пока существует привязка. По умолчанию он также повышает приоритет процесса сервера, чтобы он соответствовал приоритету клиента.BIND_NOT_FOREGROUND– запрещает повышать приоритет планирования (приоритет ЦП) для процесса целевого сервиса до уровня активного. Однако он по-прежнему позволяет повышать приоритет памяти (oom_score_adj). Это полезно для фоновых задач, которые не должны конкурировать с интерфейсом за ресурсы ЦП, но при этом не должны быть завершены.BIND_WAIVE_PRIORITY– очень строгий флаг, который указывает системе не влиять на приоритет планирования или управления памятью целевого процесса. Процесс сервиса будет управляться так, как если бы он был обычным фоновым процессом в списке LRU, что делает его доступным для завершения OOM, даже когда он связан.BIND_ABOVE_CLIENT– сервис важнее, чем само клиентское приложение. Когда системе потребуется освободить память, она в первую очередь закроет клиентское приложение, а не привязанную службу. Это "более строгий" вариант, чемBIND_AUTO_CREATE, поскольку он обеспечивает дополнительный уровень защиты сервиса за счет клиента.BIND_NOT_PERCEPTIBLE– снижает важность целевого сервиса до уровня нижеPERCEPTIBLE, позволяя системе освободить память для более критических процессов, заметных пользователю.
Практика: наблюдение за эффектами связывания
Мы будем использовать приложение MemoryLab, чтобы продемонстрировать, как привязка из приложения TOP влияет на состояние отдельного процесса.
1. Запустите MemoryLab.
Следующая команда запускает приложение. После его открытия убедитесь, что приложение остается на переднем плане (не нажимайте кнопку "Домой" и не переключайтесь между приложениями).
adb shell am start -n com.android.memorylab/.MainActivity
2. Определение процессов
Проверьте состояния процессов перед привязкой. Основной интерфейс MemoryLab работает в одном процессе, а RemoteService – в процессе :remote.
adb shell dumpsys activity processes com.android.memorylab
Пример фрагмента выходных данных:
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
Вы увидите основной процесс com.android.memorylab в состоянии TOP. Процесс :remote ещё не начат.
3. Привязка триггера
Отправьте в приложение широковещательное сообщение, чтобы запустить привязку сервиса:
adb shell am broadcast -a com.android.memorylab.LEAK_BINDER
4. Проверьте, повышен ли статус
Снова проверьте состояния процессов:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
Пример фрагмента выходных данных:
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
Процесс :remote запущен и находится в состоянии BTOP (Bound TOP) с oom_score_adj, равным 100. Это значительно более высокий уровень защиты, чем у обычной фоновой службы (500 или выше). Обозначение <=Proc{...} показывает, какой процесс отвечает за повышение приоритета.
5. Переместить на задний план
Нажмите кнопку HOME на устройстве. Проверьте состояния ещё раз:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
Пример фрагмента выходных данных:
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
Теперь оба процесса имеют более низкий приоритет (PREV / oom_score_adj 700), поскольку клиентский процесс больше не TOP. Примечание. LAST в дампе состояния относится к внутреннему состоянию LAST_ACTIVITY, которое сопоставляется с PREV в сводках высокого уровня.
Анализ с помощью procstats
Инструмент procstats позволяет посмотреть историю этих статусов.
# View stats for MemoryLab over the last hour
adb shell dumpsys procstats --hours 1 com.android.memorylab
Пример фрагмента выходных данных:
* 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%
Здесь Bnd Top – это процент времени, в течение которого удаленный процесс был связан с приложением в состоянии TOP.
Как записывать и анализировать привязки с помощью Perfetto
dumpsys дает общую картину, а Perfetto позволяет увидеть точный момент связывания и изменение оценки OOM в реальном времени.
1. Как записать трассировку
Используйте конфигурацию, которая включает linux.process_stats и категорию 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. Переходы по оценке OOM запроса
С помощью PerfettoSQL можно посмотреть, как изменилась оценка OOM удаленного процесса по сравнению с процессом пользовательского интерфейса:
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. Определите события привязки
Чтобы узнать, когда именно была установлена зависимость связывания и какой процесс ее инициировал, используйте следующий запрос:
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%';
Привязки системы к приложению
Сама система Android часто связывается со службами в сторонних приложениях, чтобы обеспечить работу основных функций. Часто цель таких привязок – сокращение задержки. Если процесс остается активным и находится в памяти, система избегает затратных накладных расходов, связанных с холодным запуском (загрузка APK, инициализация среды выполнения и создание объекта приложения), когда происходит критическое взаимодействие с пользователем. Другие привязки существуют для предотвращения частых холодных запусков приложений, которым необходимо обрабатывать потоки фоновых событий.
Вот несколько примеров из реальной жизни, которые можно наблюдать на обычном устройстве:
VoiceInteractor
Пользователи ожидают, что цифровой помощник будет встроен в ОС телефона, его можно будет вызвать мгновенно с помощью голосовой команды или быстрого жеста, а взаимодействие с ним будет плавным и беспроблемным.
Когда происходит триггер помощника (например, ключевое слово "Окей, Google" на телефонах Google Pixel), цифровой помощник должен ответить мгновенно. Для этого system_server поддерживает постоянную связь с сервисом голосового взаимодействия, выбранным пользователем.

Если вы проверите состояния процессов (например, с помощью dumpsys activity processes), то можете увидеть процесс com.google.android.googlequicksearchbox:interactor в состоянии BFGS (связанная активная служба), который поддерживается привязкой из system_server (UID 1000).
NotificationListenerService
Для некоторых связей между системой и приложением цель состоит не в задержке, а в предотвращении частых холодных запусков.
NotificationListenerService – сервис, который получает вызовы от системы при публикации или удалении новых уведомлений. Обычный пользователь смартфона может получать сотни
уведомлений в течение дня. Если система отключается от прослушивателя уведомлений, процесс приложения, скорее всего, переходит в кешированное состояние и может быть завершен LMK.
Когда поступит следующее уведомление (возможно, через несколько секунд), системе придется снова запускать процесс приложения с нуля, чтобы доставить событие. Постоянное завершение и холодный запуск процессов потребляют гораздо больше ресурсов ЦП и батареи, чем простое поддержание процесса в фоновом режиме.
Экран -1 (лента новостей) в лаунчере.
Современные приложения-лаунчеры обычно сочетают в себе основные функции навигации (значки и виджеты на главном экране) с лентой новостей, которая доступна на одном из экранов лаунчера и легко интегрируется с его интерфейсом. Лента новостей может предоставляться другим приложением. Например, на устройствах Google Pixel панель запуска интегрирована с лентой, которая предоставляется приложением Google.
Когда вы проводите влево по главному экрану, чтобы посмотреть ленту новостей, переход должен быть плавным. Для этого он подключается к интерфейсу сервиса в приложении, которое предоставляет ленту новостей, и поддерживает это подключение, пока работает сам. Это позволяет сохранять контент ленты в памяти, даже когда вы не смотрите на него.
Другие распространенные примеры
- Панель запуска (HOME_APP_ADJ). У панели запуска (главного экрана) есть собственное место в списке приоритетов. Хотя он не всегда связан с сервисом, ему назначается
HOME_APP_ADJ(обычно 600). Система предпочитает поддерживать работу панели запуска, поскольку пользователь часто к ней возвращается. Система скорее закроет ранее использовавшееся приложение (PREV_APP_ADJ = 700), чем панель запуска, потому что в противном случае при выходе из любого приложения пользователю придется ждать холодного запуска панели. - Редактор методов ввода (IME). Когда вы вводите текст, система подключается к выбранному приложению клавиатуры, например Gboard. Это позволяет поддерживать процесс клавиатуры в повышенном состоянии, даже если клавиатура временно скрыта. Это позволяет клавиатуре мгновенно появляться снова, когда вы нажимаете на другое текстовое поле.
- Платежи через NFC. Когда вы прикладываете телефон к терминалу, система связывается с платежным сервисом, например Google Кошельком. К таким транзакциям часто предъявляются строгие требования по обработке в реальном времени со стороны терминала продавца. Если платежное приложение запускается с нуля, время ожидания транзакции может истечь и она не будет выполнена.
Компромиссы и резкое падение производительности
Хотя привязки необходимы для производительности и корректности, они влияют на состояние памяти системы.
- Снижение гибкости. Каждый связанный процесс – это процесс, который LMK не может легко завершить. Это уменьшает "подушку" кешированных процессов, которые система может использовать для освобождения памяти при ее нехватке.
- Усугубление проблем с производительностью. Если привязано слишком много процессов, в системе может почти не остаться фоновых процессов, которые можно завершить. При увеличении нагрузки на память производительность системы будет падать гораздо быстрее, поскольку ей придется завершать более важные процессы или очищать кеш страниц.
Распространенные антипаттерны привязки сервисов
Поскольку привязки сервисов напрямую повышают oom_score_adj, незначительные ошибки жизненного цикла, связанные с получением или структурированием привязок, могут на несколько часов закрепить большие объемы памяти в привилегированных состояниях (BTOP, BFGS или PERC). При разработке или аудите привязанных служб остерегайтесь следующих распространенных антипаттернов.
Забытый unbindService() в долгоживущих клиентах
Привязка к сервису из синглтона Application, фонового менеджера или Activity, который вызывает bindService() в onStart() без соответствующего unbindService() в onStop(), приводит к утечке ServiceConnection.
Пока привязка активна, целевой процесс наследует повышенный приоритет клиента. Если клиент – это постоянный системный компонент или активное приложение, процесс привязанной службы остается закрепленным в BFGS или BTOP на неопределенный срок (показывая почти 100 % в procstats). Это не позволяет LMK освободить память, даже если служба полностью бездействует.
Решение. Ограничьте привязки жизненным циклом компонента, которому они нужны, или реализуйте тайм-аут, который вызывает unbindService() после периода бездействия. Не отменяйте и не восстанавливайте привязку при каждом вызове RPC, так как это приводит к перегрузке процессов и дополнительным затратам на повторную настройку Binder. Вместо этого объединяйте пакеты задач с помощью короткого таймера бездействия (например, от 5 до 30 секунд).
Совместное размещение привязанной службы с интерфейсом, интенсивно использующим память
По умолчанию все компоненты APK выполняются в одном процессе. Если ваше приложение предоставляет легковесную привязанную службу (например, NotificationListenerService, поставщика виджетов или службу плагинов, к которой привязывается система или средство запуска) в том же процессе, что и основной Activity, весь процесс наследует повышенное состояние службы (BFGS или PERC, обычно oom_score_adj со значением 200 или ниже).
Когда пользователь открывает интерфейс приложения, процесс выделяет большие иерархии View, декодированные битовые карты и графические буферы. Когда пользователь уходит со страницы, приоритет процесса не снижается до CACHED (oom_score_adj из 900 или выше), поскольку активная привязка сервиса поддерживает его на высоком уровне. Это приводит к двум
взаимосвязанным проблемам:
- Нет фонового сжатия памяти.
CachedAppOptimizerсистемы сжимает процессы только после того, как они переходят в состояниеCACHED. - Отсутствие очистки памяти LRU в кеше и освобождения памяти LMK. Система не отправляет обратные вызовы очистки фоновых процессов, связанные со списком LRU в кеше (
TRIM_MEMORY_BACKGROUNDи более поздние версии), пока привязка сервиса удерживает процесс в повышенном состоянии. Если приложение не освобождает ресурсы интерфейса в методахTRIM_MEMORY_UI_HIDDENилиActivity.onStop(), пиковые объемы памяти, выделенные для интерфейса, остаются в ОЗУ с привилегированным приоритетом OOM, при котором LMK не может легко их освободить.
Решение. Очищайте кеш пользовательского интерфейса, декодированные растровые изображения и ссылки на представления, когда интерфейс перестает быть видимым. Для этого используйте Activity.onStop(), ComponentCallbacks2.onTrimMemory(TRIM_MEMORY_UI_HIDDEN), Application.ActivityLifecycleCallbacks или ProcessLifecycleOwner. Вы также можете перенести постоянно привязанную службу в отдельный легковесный процесс, используя атрибут манифеста android:process. Тогда система сможет перевести основной процесс пользовательского интерфейса в состояние CACHED, чтобы сжать или освободить его память.
Пропуск параметров, отменяющих приоритет
Вызов bindService() только с BIND_AUTO_CREATE передает целевому сервису полный приоритет планирования и памяти вызывающего абонента. Когда активное приложение связывается с фоновой службой аналитики, ведения журнала или предварительной выборки, используя только BIND_AUTO_CREATE, оно непреднамеренно повышает приоритет этого фонового процесса до BTOP.
Решение. При привязке к вспомогательным или негарантированным сервисам, которым не требуется такой же уровень защиты, как у интерфейса переднего плана, используйте BIND_AUTO_CREATE вместе с BIND_WAIVE_PRIORITY, BIND_NOT_FOREGROUND или BIND_NOT_PERCEPTIBLE. Это позволит системе управлять целевым процессом в списке LRU.
← Локальность | ↑ Вверх | На уровне системы →