Разработка
Приоритизация эффективности использования памяти: важные шаги для Android 17
Чтобы обеспечить стабильность устройства, начиная с Android 17 система начнёт принудительно применять лимиты памяти для приложений с учётом общего объёма RAM устройства.
Google анонсирует важные изменения в работе с памятью в Android 17:
Хотя производительность приложения часто ассоциируют с плавным интерфейсом и быстрым стартом, именно память служит «тихим фундаментом», на котором строятся все эти видимые метрики. Не секрет, что мы наблюдаем сдвиг: объём и использование памяти устройства становятся важнее, чем когда-либо. В Android 17 мы не только продвинулись в оптимизациях памяти на уровне системы, но и предоставляем инструменты и API, которые помогут вам заранее подготовиться к более строгим требованиям к памяти позднее в этом году.
Чтобы обеспечить стабильность устройства, начиная с Android 17 система начнёт принудительно применять лимиты памяти для приложений с учётом общего объёма RAM устройства. Если приложение превысит эти лимиты, Android завершит процесс без какого-либо stack trace.
Помимо таких принудительных завершений, неоптимизированное использование памяти неизбежно ухудшает пользовательский опыт. Когда приложение приближается к лимитам heap-памяти, запускается более частая сборка мусора, что приводит к заметным фризам и подёргиваниям UI. Кроме того, когда на устройстве заканчивается доступная память, система начинает срочно освобождать страницы памяти, что создаёт нагрузку на CPU, увеличивает задержки интерфейса и расход батареи. Если нехватка памяти становится слишком серьёзной, это может вызвать события Low Memory Killer, LMK, которые внезапно завершают фоновые процессы, вынуждая приложения дольше запускаться «с нуля» и терять пользовательское состояние.
Чтобы создавать действительно производительные приложения и избежать таких принудительных завершений, мы рекомендуем использовать следующие стратегии оптимизации памяти:
- Максимизируйте оптимизацию байткода с помощью R8.
- Оптимизируйте загрузку изображений.
- Находите и исправляйте утечки памяти с помощью Android Studio.
- Освобождайте память, когда приложение выходит из видимого состояния.
- Используйте расширенное наблюдение за памятью через ProfilingManager.
Краткая версия этой статьи также доступна в видеоформате — посмотрите её.
Понимание лимитов памяти приложений в Android 17
Лимиты памяти для приложений вводятся в Android 17, чтобы не позволить одному «плохому актору» разрушить многозадачность и стабильность всего пользовательского устройства.
Ниже приведены основные причины такого архитектурного изменения.
- Предотвращение каскадных завершений процессов. Когда приложение разрастается или обретает утечку памяти, находясь в привилегированном состоянии, например при работающем Foreground Service, оно изначально защищено от системного Low Memory Killer. По мере того как одно приложение бесконтрольно растёт и удерживает RAM, LMK вынужден компенсировать это, завершая десятки небольших, корректно работающих кешированных приложений и фоновых задач, чтобы освободить место для приложения, потребляющего слишком много памяти.
- Сохранение многозадачности и пользовательского состояния. Когда система вынуждена очищать cached-приложения ради одного процесса с утечкой памяти, опыт многозадачности сильно деградирует. Пользователь, возвращаясь к недавно открытым приложениям, получает медленный cold start вместо почти мгновенного warm resume. Такая неэффективность создаёт дополнительную нагрузку на CPU и ускоряет разряд батареи. Также может быть потерян пользовательский контекст в недавно использованных приложениях: позиция прокрутки, navigation stack, прогресс в игре и так далее.
Чтобы определить, была ли сессия вашего приложения затронута этими ограничениями «в поле», можно вызвать getDescription() у ApplicationExitInfo. Если система применила лимит, причина завершения будет указана как REASON_OTHER, а строка description будет содержать "MemoryLimiter:AnonSwap".
Также можно использовать trigger-based profiling с TRIGGER_TYPE_ANOMALY, чтобы автоматически собирать heap dump при достижении лимита памяти. Кроме того, Android активно работает над тем, чтобы показывать разработчикам больше in-field метрик памяти в Google Play Console.
Документация по лимитам памяти также была расширена: теперь она включает команды для локальной отладки, которые позволяют симулировать ограничения памяти в локальной среде и проверять поведение приложения при любом сценарии принудительного применения лимита.
Максимизируйте оптимизацию байткода с помощью R8
Один из самых эффективных способов уменьшить memory footprint приложения — включить оптимизатор R8. За счёт сокращения классов, методов и полей до более коротких имён, а также удаления неиспользуемого кода и ресурсов R8 значительно снижает потребление памяти приложением, уменьшая объём resident code, необходимого во время выполнения.
R8 минимизирует хранящийся в памяти код, снижает отпечаток в памяти и уменьшает риск завершения приложения через LMK. В результате чаще происходят теплые старты вместо медленных холодных. Кроме того, оптимизированный байткод снижает нагрузку на CPU в главном потоке, напрямую уменьшая частоту 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. - Если в кодовой базе используется reflection, добавьте keep rules, чтобы R8 не оптимизировал соответствующие части кода. При этом keep rules нужно сужать максимально точечно, чтобы сохранить максимум оптимизаций.
Чтобы получить максимальную оптимизацию, следуйте этим практикам в файле keep rules:
- Удалите глобальные опции вроде
-dontoptimize,-dontshrinkи-dontobfuscate, которые запрещают R8 оптимизировать всю кодовую базу. - Удалите keep правила, которые мешают оптимизации Android-компонентов вроде
Activity,Service,ViewилиBroadcastReceiver. - Уточните широкие package-wide keep rules так, чтобы они таргетировали только конкретные классы или методы.
Больше рекомендаций можно найти в документации по keep rules.
Лучшие практики R8 для разработчиков библиотек
Если вы разрабатываете библиотеку, строго помещайте правила, которые нужны вашим потребителям, в consumer rules-файл, а внутренние правила защиты самой библиотеки — в proguard-rules.pro.
Подробнее об оптимизации библиотек можно узнать в материалах про оптимизацию библиотек
R8 Configuration Analyzer
Чтобы провести аудит оптимизации R8, используйте Configuration Analyzer. Он показывает текущее состояние оптимизации с оценками Obfuscation, Optimization и Shrinking.
С помощью Configuration Analyzer можно понять, сколько классов, методов или полей исключены из оптимизации каждым keep rule. Уточняйте слишком широкие package-wide правила, чтобы разблокировать максимальную оптимизацию.
Также Configuration Analyzer помогает находить keep rules, которые перекрывают другие правила, избыточные keep rules и неиспользуемые keep rules.

R8 Agent Skill
Также можно использовать R8 Agent Skill с Android Studio agent или другими AI-инструментами, чтобы находить неправильные конфигурации и уточнять правила. Это может улучшить производительность приложения.
При этом выводы AI-инструментов требуют технической проверки.
Оптимизируйте загрузку изображений
Обычно bitmaps — это самые крупные распространённые объекты, которые находятся в памяти приложения. Они представляют собой финальную стадию процесса загрузки изображения: сжатые файлы, например JPEG или PNG, декодируются в raw pixel data для отображения.
Это означает, что маленькое сжатое изображение размером 100 КБ может превратиться в несколько мегабайт RAM, потому что потребление памяти определяется pixel dimensions и color depth. Поскольку операции с bitmap часто находятся на критическом пути в отрисовке кадров, неоптимизированные изображения приводят к серьёзному раздуванию памяти и задержкам в UI.
Google рекомендует использовать библиотеки загрузки изображений: Coil для Kotlin-first проектов, особенно при разработке с Jetpack Compose, и Glide для Java-based приложений.
Пять лучших практик
- Даунсемплинг. Если вы загружаете bitmaps вручную, не стоит загружать огромное изображение в маленькую thumbnail view. Используйте
inSampleSize, чтобы загрузить уменьшенную версию. Glide и Coil выполняют downsampling по умолчанию, а стратегию можно настроить черезDownsampleStrategyиImageLoaderсоответственно. - Обрезка. Не встраивайте padding напрямую в файл изображения для уменьшения, например не добавляйте прозрачную рамку, чтобы искусственно увеличить размеры изображения. Вместо «запекания» таких границ используйте
InsetDrawableили применяйте padding прямо воViewилиComposable, который содержит bitmap. - Конфигурация. Балансируйте качество и потребление памяти, выбирая правильный pixel format. Используйте
RGB_565, если прозрачность не нужна: он использует в два раза меньше памяти, чем дефолтныйARGB_8888. В Glide это можно настроить черезDecodeFormat, а в Coil — через свойствоbitmapConfig. - Приоритет векторным изображениям. Для базовых геометрических ассетов используйте
ShapeDrawableкак легковесную альтернативу декодированию растровых ресурсов. Определив такие ассеты один раз в XML, вы обеспечите масштабирование на разных density и фактически устраните раздувание в памяти, вызванное ресурсами. - Переиспользование. Если приложение управляет
Bitmapвручную, минимизируйте потребление: когда bitmap больше не нужен, вызовитеbitmap.recycle()и сразу удалите ссылку наBitmap. Если вы используете библиотеку загрузки изображений вроде Glide или Coil, возвращайте bitmap в managed pool библиотеки. Предоставляя уже существующий буфер для будущих потребностей, pool избегает лишних аллокаций.
Подробнее можно посмотреть в документации по оптимизации производительности изображений.
Инструменты Android Studio
Также можно устранять дублирующиеся bitmaps с помощью Android Studio Narwhal 4. Вот как найти их за пять шагов:
- Откройте вкладку Profiler в Android Studio.
- Нажмите Heap Dump или Analyze Memory Usage и запустите запись, чтобы сделать снепшот текущего состояния памяти приложения.
- Просмотрите результаты анализа и найдите жёлтый предупреждающий треугольник ⚠️, которым Android Studio помечает дублированные битмапы, хранящиеся в памяти несколько раз. Либо в header профайлера выберите Filter by и укажите Duplicate Bitmaps.
- Нажмите на любой помеченный элемент, чтобы открыть Bitmap Preview и увидеть, какое именно изображение дублируется.
- Используйте это визуальное подтверждение, чтобы найти в коде избыточную логику загрузки и реализовать более корректную стратегию кэширования.
Ищите жёлтый предупреждающий треугольник ⚠️ в heap dumps при использовании Android Studio Profiler.
Находите и исправляйте утечки памяти с помощью Android Studio
Утечки памяти в Android возникают, когда код удерживает ссылку на объект намного дольше, чем длится его жизненный цикл. Это не даёт Garbage Collector освободить память и со временем приводит к деградации производительности или OutOfMemoryError.
Android Studio Panda 3 включает специальную profiler-задачу LeakCanary, позволяющую разработчикам анализировать утечки памяти в реальном времени и сопоставлять leak traces прямо внутри IDE.
Profiler-задача LeakCanary в Android Studio переносит анализ утечек памяти с устройства на машину разработчика. В результате анализ выполняется значительно быстрее по сравнению с on-device leak analysis.
Кроме того, анализ утечек теперь контекстуализирован внутри IDE и полностью интегрирован с исходным кодом. Это даёт возможности вроде Go to declaration и другие полезные связи с кодом, что сильно уменьшает трение и время, необходимое на расследование и исправление утечек.
Примеры распространённых утечек памяти
Утечки памяти возникают, когда объект остаётся в памяти дольше предполагаемого срока жизни. Обычно это происходит из-за:
- удержания ссылок на
Fragment,ActivityилиView, которые больше не используются; - неправильного управления ссылками на
Context; - отсутствия корректной отписки от observers, listeners и receivers;
- создания static-ссылок на объекты, привязанные к компонентам с более коротким lifecycle.
Освобождайте память, когда приложение выходит из видимого состояния
Android может освободить память вашего приложения или полностью остановить приложение, если это необходимо для освобождения памяти под критически важные задачи, как описано в обзоре управления памятью.
Обычно Android освобождает память приложения, когда оно не видно пользователю: например, отбрасывает часть кода и data pages из памяти или сжимает выделенную кучу. Когда пользователь возвращается в приложение и оно пытается обратиться к памяти, которая была освобождена, ОС подгружает её обратно по запросу. Такой свап может быть медленным и вызывать неожиданные заминки и задержки.
Если оставить ОС самой решать, какую память освободить, может оказаться, что она освободила именно ту память, которая понадобится приложению сразу после возобновления работы. Вместо этого приложение может добровольно сбрасывать выделенную память, которую можно позже недорого восстановить on demand.
Для этого можно реализовать интерфейс ComponentCallbacks2. Его можно использовать в Activity, Fragment, Service или даже в вашем кастомном классе Application. Использование в Application особенно эффективно для глобального управления кэшами.
Callback onTrimMemory() уведомляет приложение о lifecycle- или memory-related событиях, которые являются хорошим моментом для добровольного сокращения потребления памяти.
С точки зрения memory lifecycle management, реализация должна фокусироваться исключительно на TRIM_MEMORY_UI_HIDDEN и TRIM_MEMORY_BACKGROUND. Начиная с Android 14, система перестала отправлять уведомления для других legacy-констант, которые были официально deprecated в Android 15.
TRIM_MEMORY_UI_HIDDEN означает, что UI приложения больше не виден пользователю. Это возможность освободить значительные memory allocations, строго связанные с интерфейсом: bitmaps, буферы видеоплеера, сложные animation resources и так далее.
TRIM_MEMORY_BACKGROUND означает, что процесс находится в фоне и теперь является кандидатом на завершение, если системе понадобится глобально освободить память. Чтобы продлить время нахождения процесса в cached-состоянии и уменьшить количество холодных стартов, нужно агрессивно освобождать любые ресурсы, которые можно легко пересоздать при возвращении пользователя.
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
Чтобы ловить и диагностировать memory issues «в поле», которые не удаётся воспроизвести локально, используйте API ProfilingManager. Этот продвинутый API наблюдения, появившийся в Android 15, позволяет программно собирать real-user Perfetto profiles.
Для команд, у которых нет собственной инфраструктуры для управления и хостинга артефактов быстродействия, Crashlytics предлагает специализированное решение, которое должно упростить этот поток. Разработчиков приглашают поделиться обратной связью.
Android 17 добавляет новые event-driven триггеры, в первую очередь TRIGGER_TYPE_OOM и TRIGGER_TYPE_ANOMALY.
OOM trigger автоматически собирает Java heap dump в момент падения из-за OutOfMemoryError, предоставляя точное состояние аллокаций. Собранный OOM profile становится доступен при следующем запуске приложения после регистрации колбека registerForAllProfilingResults.
Anomaly trigger обнаруживает серьёзные проблемы производительности, например excessive binder spam или превышение memory thresholds. Memory anomaly передаёт heap dump непосредственно перед тем, как система завершит приложение.
val profilingManager =
applicationContext.getSystemService(ProfilingManager::class.java)
val triggers = ArrayList()
triggers.add(ProfilingTrigger.Builder(
ProfilingTrigger.TRIGGER_TYPE_ANOMALY))
val mainExecutor: Executor = Executors.newSingleThreadExecutor()
val resultCallback = Consumer { 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)
После того как heap dump собран, его можно скачать с сервера или локально через adb pull, а затем перетащить файл в Perfetto UI.
Чтобы упростить процесс отладки памяти, используйте Heap Dump Explorer — это новый default view для heap dumps в Perfetto UI. Инструмент предоставляет удобный интерфейс для инспекции Java heap dumps: позволяет визуализировать иерархии аллокаций объектов, вычислять retained memory size и находить кратчайший путь от GC root.
С помощью Heap Dump Explorer можно быстро находить утечки памяти, раздутые retained объекты, например чрезмерные потребления памяти на изображения, и анализировать выделение памяти в куче на объекты в одном месте.

Заключение
Оптимизация байткода с помощью R8, применение лучших практик загрузки изображений и устранение утечек памяти — критически важные шаги для качественного пользовательского опыта и эффективного управления ресурсами под нагрузкой.
Эти проактивные меры помогают поддерживать стабильность и производительность приложения, предотвращают неожиданные завершения процессов и сохраняют пользовательский контекст.
Чтобы глубже разобраться в производительности, изучите обновлённые рекомендации по работе с памятью.
-
Новости3 недели назадВидео и подкасты о мобильной разработке 2026.20
-
Видео и подкасты для разработчиков3 недели назадОт личной продуктивности к командной: сила шаблонизации в IDE
-
Новости4 недели назадВидео и подкасты о мобильной разработке 2026.19
-
Вовлечение пользователей4 недели назадЭкран после покупки: как предотвратить отмены в День 0
