Хотя производительность приложений часто ассоциируется с плавным пользовательским интерфейсом и быстрым запуском, память служит негласным фундаментом, на котором строятся эти видимые показатели. Не секрет, что мы наблюдаем сдвиг, в сторону большей важности памяти устройства, чем когда-либо. Мы не только добились значительных успехов в оптимизации памяти Android в Android 17, но и предоставляем инструменты и поддержку API, которые помогут вам опередить более строгие требования к памяти, действующие в этом году.
Для обеспечения стабильности работы устройства, начиная с Android 17, система начнет устанавливать ограничения на использование памяти приложениями в зависимости от общего объема оперативной памяти устройства. Если приложение превысит эти ограничения, Android завершит процесс без указания трассировки стека.
Помимо этих принудительных завершений, неоптимизированное использование памяти неизбежно ухудшает пользовательский опыт. Когда приложение приближается к пределам памяти кучи, запускается частая сборка мусора, что приводит к заметным подтормаживаниям пользовательского интерфейса. Кроме того, когда на устройстве заканчивается доступная память, система пытается освободить страницы, что вызывает нагрузку на процессор, задержки в работе пользовательского интерфейса и разрядку батареи. Если нехватка памяти слишком серьезна, это может вызвать события Low Memory Killer (LMK), которые резко завершают фоновые процессы и приводят к медленному холодному запуску приложений и потере пользовательского состояния.
Для создания высокопроизводительных приложений и предотвращения таких принудительных завершений работы мы рекомендуем использовать следующие стратегии оптимизации памяти:
- Максимальная оптимизация байт-кода с помощью R8
- Оптимизировать загрузку изображений
- Обнаружение и устранение утечек памяти с помощью Android Studio
- Очищайте память, когда приложение выходит из видимого состояния.
- Расширенные возможности мониторинга памяти с помощью ProfilingManager
Сокращенная версия этой статьи также доступна в видеоформате, посмотрите!
Понимание ограничений памяти приложений в Android 17
В Android 17 вводятся ограничения на использование памяти приложениями, чтобы предотвратить ситуацию, когда «один злоумышленник» может нарушить работу многозадачности и стабильность всего устройства пользователя.
Вот краткий обзор причин, побудивших к этим архитектурным изменениям:
- Предотвращение каскадных завершений : Когда приложение разрастается или утекает память, находясь в привилегированном состоянии (например, выполняя фоновую службу), оно изначально защищено от системного механизма Low Memory Killer (LMK). По мере того, как это единственное приложение бесконтрольно разрастается и накапливает оперативную память, LMK вынужден компенсировать это, завершая работу десятков более мелких, корректно работающих кэшированных приложений и фоновых задач, чтобы освободить место для приложения, потребляющего много памяти.
- Сохранение многозадачности и состояния пользователя: Когда система вынуждена очищать кэшированные приложения для обеспечения работы единственного процесса, вызывающего утечку памяти, качество многозадачности значительно ухудшается. Пользователи, возвращающиеся к ранее кэшированным приложениям, сталкиваются с медленным холодным запуском вместо почти мгновенного возобновления работы. Эта неэффективность создает дополнительную нагрузку на процессор и ускоряет разрядку батареи. Она также может нарушить контекст пользователя в недавно использованных приложениях, например, положение прокрутки, стеки навигации и игровой прогресс.
Чтобы определить, повлияли ли эти ограничения на работу вашего приложения, вы можете вызвать метод getDescription() внутри ApplicationExitInfo . Если система применила ограничение, причина выхода будет указана как REASON_OTHER , а строка описания будет содержать "MemoryLimiter:AnonSwap". Вы также можете использовать профилирование на основе триггеров с помощью TRIGGER_TYPE_ANOMALY для автоматического создания дампов памяти при достижении лимита памяти. Кроме того, Android активно работает над тем, чтобы предоставлять разработчикам больше метрик памяти, получаемых непосредственно в процессе работы приложения, в консоли Google Play.
Мы также расширили документацию по ограничениям памяти, включив в нее команды локальной отладки, позволяющие имитировать ограничения памяти в локальной среде и проверять поведение вашего приложения при любых ограничениях памяти.
Максимальная оптимизация байт-кода с помощью R8
Одним из наиболее эффективных способов уменьшить потребление памяти вашим приложением является включение оптимизатора R8. Сокращая имена классов, методов и полей, а также удаляя неиспользуемый код и ресурсы, R8 значительно уменьшает потребление памяти вашим приложением, минимизируя объем резидентного кода, необходимого во время выполнения.
R8 минимизирует резидентный код, уменьшая объем используемой памяти и снижая риск завершения работы LMK. Это приводит к более частым «теплым» запускам по сравнению с медленными «холодными» запусками. Кроме того, оптимизированный байт-код снижает нагрузку на ЦП основного потока, напрямую уменьшая частоту ANR и обеспечивая более плавную работу приложения. Например, цифровой банк Monzo, внедрив полную оптимизацию R8, добился снижения частоты ANR на 35%, улучшения частоты «холодных» запусков на 30% и уменьшения общего размера приложения на 9%.

Для правильной настройки R8 в файле build.gradle :
- Установите
isShrinkResources = trueиisMinifyEnabled = true. - Используйте
proguard-android-optimize.txtвместо устаревшего файлаproguard-android.txt, который фактически предотвращает оптимизацию и больше не поддерживается в Android Gradle Plugin 9. - Удалите
android.enableR8.fullMode = falseиз файлаgradle.properties.
Если вы используете рефлексию в своем коде, добавьте правила Keep , чтобы предотвратить оптимизацию этих частей кода программой R8. Убедитесь, что область действия правил Keep узкая, чтобы добиться максимальной оптимизации.
Для достижения максимальной оптимизации обязательно следуйте этим рекомендациям в файле правил сохранения.
- Удалите глобальные параметры, такие как
-dontoptimize,-dontshrinkи-dontobfuscate, которые мешают R8 оптимизировать весь код. - Удалите правила сохранения, которые препятствуют оптимизации компонентов Android, таких как Activity, Services, Views или Broadcast receivers.
- Уточните общие правила сохранения данных для всего пакета, чтобы они применялись только к определенным классам или методам.
Чтобы ознакомиться с дополнительными рекомендациями, просмотрите нашу документацию по правилам хранения данных .
Рекомендации для разработчиков библиотек по использованию R8
Если вы являетесь разработчиком библиотек, размещайте необходимые вашим пользователям правила строго в consumer-rules file , а внутренние правила защиты вашей библиотеки храните в файле proguard-rules.pro . Для получения дополнительной информации об оптимизации библиотек см. раздел «Оптимизация для авторов библиотек» .
Анализатор конфигурации R8
Для проверки оптимизации вашей системы R8 используйте анализатор конфигурации . Анализатор конфигурации отображает текущее состояние оптимизации. Показатели обфускации, оптимизации и сокращения. С помощью анализатора конфигурации вы также можете понять, сколько классов, методов или полей блокируется для оптимизации каждым правилом сохранения. Уточните эти общие правила сохранения для всего пакета, чтобы добиться максимальной оптимизации.
С помощью анализатора конфигурации вы также можете выявить правила сохранения, которые поглощают другие правила сохранения, избыточные правила сохранения и неиспользуемые правила сохранения.

Навык агента R8
Вы также можете использовать навык R8 Agent с агентом Android Studio или другими инструментами искусственного интеллекта для устранения ошибок конфигурации и уточнения правил, что приведет к повышению производительности приложения. (Для получения результатов от навыков, использующих ИИ, потребуется техническая проверка).
Оптимизировать загрузку изображений
Растровые изображения обычно являются самыми большими распространенными объектами, хранящимися в памяти вашего приложения. Они представляют собой заключительный этап процесса загрузки изображения, на котором сжатые файлы, такие как JPEG или PNG, декодируются в необработанные пиксельные данные для отображения. Это означает, что крошечное сжатое изображение размером 100 КБ может разрастись до нескольких мегабайт оперативной памяти, поскольку потребление памяти определяется размерами пикселей изображения и глубиной цвета. Поскольку операции с растровыми изображениями часто находятся на критическом пути отрисовки кадров, неоптимизированные изображения приводят к значительному раздуванию памяти и замедлению работы пользовательского интерфейса.
Google рекомендует использовать библиотеки для загрузки изображений Coil для проектов, ориентированных на Kotlin, особенно при разработке с помощью Jetpack Compose, и Glide для приложений на Java.
Примените эти пять передовых методов.
- Уменьшение разрешения изображений: если вы загружаете растровые изображения вручную, избегайте загрузки большого изображения в крошечное окно миниатюры; используйте inSampleSize для загрузки уменьшенной версии. Glide и Coil по умолчанию уменьшают разрешение изображений, и вы можете настроить эту стратегию уменьшения разрешения с помощью DownsampleStrategy и ImageLoader соответственно.
- Обрезка: Избегайте встраивания отступов непосредственно в файл изображения для создания эффекта черных полос (например, для увеличения размеров изображения). Вместо запекания этих границ используйте InsetDrawable или применяйте отступы непосредственно в View или Composable, содержащем растровое изображение.
- Настройки: Сбалансируйте использование памяти и качество, выбрав правильный формат пикселей. Используйте
RGB_565если прозрачность не требуется, поскольку он занимает вдвое меньше памяти, чем форматARGB_8888по умолчанию. В Glide это можно настроить с помощью DecodeFormat , а в Coil — с помощью свойства bitmapConfig . - Отдавайте приоритет векторным изображениям: для простых геометрических объектов используйте ShapeDrawable в качестве облегченной альтернативы декодированию растровых изображений. Определяя эти объекты один раз с помощью XML, вы обеспечиваете их бесперебойное масштабирование при любой плотности экрана, эффективно устраняя при этом избыточное потребление памяти, обусловленное расходом ресурсов.
- Повторное использование: Если ваше приложение управляет растровыми изображениями вручную, то для минимизации расхода памяти, когда растровое изображение больше не требуется, приложение должно вызвать
bitmap.recycle()и немедленно удалить ссылкуBitmap. Если вы используете библиотеку загрузки изображений, такую как Glide или Coil, верните растровое изображение в управляемый пул библиотеки. Предоставляя существующий буфер для будущих потребностей в памяти, пул эффективно избегает накладных расходов на новые выделения памяти.
Для получения более подробной информации ознакомьтесь с нашей документацией по оптимизации производительности обработки изображений .
Инструменты Android Studio
Также можно удалить лишние растровые изображения с помощью Android Studio Narwhal 4. Вот как это сделать за пять простых шагов:
- Откройте вкладку «Профайлер» в Android Studio.
- Нажмите «Дамп кучи» (или «Анализ использования памяти») и нажмите кнопку записи, чтобы сделать снимок текущего состояния памяти вашего приложения.
- Просмотрите результаты анализа на наличие желтого предупреждающего треугольника ⚠️, который Android Studio использует для обозначения дубликатов растровых изображений, хранящихся несколько раз. В качестве альтернативы перейдите в заголовок профилировщика, выберите «Фильтровать по:» и выберите параметр «Дубликаты растровых изображений».
- Щелкните по любой отмеченной записи, чтобы открыть панель предварительного просмотра растрового изображения , которая позволит вам точно увидеть, какое изображение является повторяющимся нарушителем.
- Используйте это визуальное подтверждение, чтобы выявить избыточную логику загрузки в вашем коде и внедрить более эффективную стратегию кэширования.

Обнаружение и устранение утечек памяти с помощью Android Studio
Утечки памяти в Android возникают, когда ваш код удерживает ссылку на объект долгое время после завершения его жизненного цикла. Это препятствует сборщику мусора (GC) освободить эту память, что в конечном итоге приводит к замедлению работы или ошибке OutOfMemoryError (OOM).
В Android Studio Panda 3 есть специальная задача профилирования LeakCanary , позволяющая разработчикам анализировать утечки памяти в реальном времени и отображать трассировки непосредственно в IDE.
Задача профилирования LeakCanary в Android Studio активно переносит анализ утечек памяти с вашего устройства на вашу машину разработчика, что приводит к значительному повышению производительности на этапе анализа утечек по сравнению с анализом утечек на самом устройстве.

Кроме того, анализ утечек теперь контекстуализирован в IDE и полностью интегрирован с исходным кодом, предоставляя такие функции, как переход к объявлению и другие полезные связи в коде, которые значительно сокращают сложности и время, необходимые для исследования и устранения утечек памяти.
Примеры распространенных утечек памяти
Утечки памяти возникают, когда объект сохраняется в памяти дольше положенного срока. Обычно это происходит по следующим причинам:
- Сохранение ссылок на фрагменты, действия или представления, которые больше не используются.
- Неправильное управление ссылками на контекст.
- Неправильная процедура отмены регистрации наблюдателей, слушателей и получателей.
- Создание статических ссылок на объекты, связанные с компонентами с более коротким жизненным циклом.
Вот несколько примеров сценариев:
| Сценарий | Пример, основанный на композиции | Пример на основе представления |
| Утечка контекста | Пример: Исправить: | Пример: Исправить: |
| Утечка слушателей | Пример: Исправить: | Пример: Исправить: |
| Утечка информации | Пример:
| Пример: Исправить: |
Очищайте память, когда приложение выходит из видимого состояния.
Android может освободить память для вашего приложения или полностью остановить его работу, если это необходимо для выполнения критически важных задач, как описано в разделе «Обзор управления памятью» . Обычно Android освобождает память для вашего приложения, когда это не видно пользователю, например, путем удаления части кода и страниц данных приложения из памяти или сжатия выделенной памяти в куче. Когда пользователь возобновляет работу приложения, и приложение пытается получить доступ к освобожденной памяти, ОС по запросу возвращает эту память обратно в подкачку. Такое поведение подкачки может быть медленным и вызывать неожиданные рывки или зависания в работе приложения.
Если вы предоставите операционной системе право решать, какую память освободить у вашего приложения, вы можете обнаружить, что ОС освободила память, которая понадобится вам вскоре после возобновления работы приложения. Вместо этого ваше приложение может добровольно отбрасывать выделенную память, которую оно сможет восстановить позже по запросу и с минимальными затратами. Для этого вы можете реализовать интерфейс ComponentCallbacks2 . Вы можете реализовать onTrimMemory в вашем Activity , Fragment , Service или даже в вашем пользовательском классе Application . Использование его в классе Application очень эффективно для глобального управления кэшем.
Предоставленный метод обратного вызова onTrimMemory() уведомляет ваше приложение о событиях жизненного цикла или связанных с памятью событиях, которые предоставляют вашему приложению хорошую возможность добровольно сократить использование памяти.
В плане управления жизненным циклом памяти ваша реализация должна сосредоточиться исключительно на TRIM_MEMORY_UI_HIDDEN и TRIM_MEMORY_BACKGROUND . Начиная с Android 14, система перестала отправлять уведомления для других устаревших констант, которые были официально объявлены устаревшими в Android 15.
TRIM_MEMORY_UI_HIDDEN : Этот сигнал указывает на то, что пользовательский интерфейс вашего приложения вышел из поля зрения пользователя. Это дает возможность освободить значительные объемы памяти, выделенные исключительно для интерфейса, такие как растровые изображения, буферы воспроизведения видео или сложные ресурсы анимации.
TRIM_MEMORY_BACKGROUND : На этом уровне ваш процесс находится в фоновом режиме и теперь может быть завершен для удовлетворения потребностей системы в глобальной памяти. Чтобы продлить время, в течение которого ваш процесс остается в кэшированном состоянии, и уменьшить количество холодных перезапусков приложения, следует активно освобождать любые ресурсы, которые можно легко восстановить после возобновления пользователем своей сессии.
import android.content.ComponentCallbacks2 // Other import statements. class MainActivity : AppCompatActivity(), ComponentCallbacks2 { /** * Release memory when the UI becomes hidden or when system resources become low. * @param level the memory-related event that is raised. */ override fun onTrimMemory(level: Int) { if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { // Release memory related to UI elements, such as bitmap caches. } if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { // Release memory related to background processing, such as by // closing a database connection. } } }
Примечание: Интеграция onTrimMemory может зависеть от поддержки SDK. Например, некоторые игры используют свой игровой движок для включения этой возможности. Пожалуйста, ознакомьтесь с документацией по оптимизации памяти в играх .
Расширенные возможности мониторинга памяти с помощью ProfilingManager
Для выявления и диагностики проблем с памятью в реальных условиях, которые невозможно воспроизвести локально, следует использовать API ProfilingManager . Этот расширенный API для мониторинга, представленный в Android 15, позволяет программно собирать профили Perfetto реальных пользователей.
Для команд, у которых отсутствует выделенная инфраструктура для управления и размещения артефактов производительности, Crashlytics изучает специализированное решение для оптимизации этого рабочего процесса. Компания приглашает разработчиков предоставить свои отзывы .
В Android 17 представлены новые триггеры, управляемые событиями , в частности, TRIGGER_TYPE_OOM и TRIGGER_TYPE_ANOMALY :
- Триггер OOM автоматически собирает дамп кучи Java в тот самый момент, когда происходит сбой OutOfMemoryError, предоставляя точные данные о состоянии выделения памяти. Собранный профиль OOM предоставляется при следующем запуске приложения и регистрации обратного вызова
registerForAllProfilingResults. - Триггер «Аномалия» обнаруживает серьезные проблемы с производительностью, такие как чрезмерное использование binder-ов или превышение пороговых значений памяти. Аномалия памяти создает дамп кучи непосредственно перед завершением работы приложения системой.
val profilingManager = applicationContext.getSystemService(ProfilingManager::class.java) val triggers = ArrayList<ProfilingTrigger>() triggers.add(ProfilingTrigger.Builder( ProfilingTrigger.TRIGGER_TYPE_ANOMALY)) val mainExecutor: Executor = Executors.newSingleThreadExecutor() val resultCallback = Consumer<ProfilingResult> { profilingResult -> if (profilingResult.errorCode != ProfilingResult.ERROR_NONE) { // upload profile result to server for further analysis setupProfileUploadWorker(profilingResult.resultFilePath) } profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback) profilingManager.addProfilingTriggers(triggers)
После получения дампа кучи вы можете загрузить профиль с сервера или локально с помощью команды adb pull и перетащить файл в пользовательский интерфейс Perfetto . Для оптимизации процесса отладки памяти используйте Heap Dump Explorer — это новый вид по умолчанию для дампов кучи в пользовательском интерфейсе Perfetto. Этот инструмент предоставляет интуитивно понятный интерфейс для анализа дампов кучи Java, позволяя визуализировать иерархии выделения объектов, вычислять размеры удерживаемой памяти и определять кратчайший путь от корня сборки мусора. Используя Heap Dump Explorer, вы можете быстро выявлять утечки памяти, раздутые удерживаемые объекты, такие как чрезмерное выделение битовых карт, и анализировать выделение объектов в куче — все в одном месте.

Заключение
Оптимизация байт-кода с помощью R8, внедрение лучших практик загрузки изображений и устранение утечек памяти — критически важные шаги для обеспечения высококачественного пользовательского опыта при эффективном управлении ресурсами в условиях высокой нагрузки. Принятие этих упреждающих мер помогает поддерживать стабильность и производительность приложения, предотвращая неожиданные завершения работы и сохраняя контекст пользователя. Для дальнейшего повышения квалификации в области производительности ознакомьтесь с нашими обновленными рекомендациями по использованию памяти .
ИнструкцииПонимая, что чрезмерный расход заряда батареи является одной из главных проблем для пользователей Android, Google предпринимает значительные шаги, чтобы помочь разработчикам создавать более энергоэффективные приложения.
Alice Yuan • 8 мин чтения
ИнструкцииРуководство по выравниванию производительности включает 5 уровней. Мы начнем с уровня 1, который знакомит с инструментами повышения производительности, требующими минимальных усилий по внедрению, и перейдем к уровню 5, идеально подходящему для приложений, которые располагают ресурсами для поддержки собственной системы управления производительностью.
Alice Yuan • 9 мин чтения
ИнструкцииПри разработке новых функций производительность приложения часто отходит на второй план. Однако, хотя разработчики не всегда задумываются об этом, пользователи могут точно увидеть, в каких областях производительность вашего приложения отстает.
Ben Weiss • 3 мин чтения
Получайте еженедельно самые свежие новости о разработке Android прямо на свою электронную почту.





