Управляйте памятью вашего приложения

На этой странице объясняется, как заранее сократить использование памяти в вашем приложении. Информацию о том, как операционная система Android управляет памятью, см. в разделе Обзор управления памятью .

Оперативная память (ОЗУ) — ценный ресурс для любой среды разработки программного обеспечения, и она еще более ценна для мобильной операционной системы, где физическая память часто ограничена. Хотя и Android Runtime (ART), и виртуальная машина Dalvik выполняют рутинную сборку мусора, это не означает, что вы можете игнорировать, когда и где ваше приложение выделяет и освобождает память. Вам по-прежнему необходимо избегать утечек памяти (обычно вызванных сохранением ссылок на объекты в статических переменных-членах) и освобождать любые объекты Reference в подходящее время, определенное обратными вызовами жизненного цикла.

Мониторинг доступной памяти и ее использования

Прежде чем устранять проблемы, вам необходимо обнаружить проблемы с использованием памяти вашим приложением. Профилировщик памяти в Android Studio помогает находить и диагностировать проблемы с памятью следующими способами:

  • Посмотрите, как ваше приложение распределяет память с течением времени. Профилировщик памяти показывает в реальном времени график того, сколько памяти использует ваше приложение, количество выделенных объектов Java и время выполнения сборки мусора.
  • Инициируйте события сборки мусора и делайте снимок кучи Java во время работы вашего приложения.
  • Запишите распределение памяти вашего приложения, проверьте все выделенные объекты, просмотрите трассировку стека для каждого выделения и перейдите к соответствующему коду в редакторе Android Studio.

Освобождение памяти в ответ на события

Android может освободить память вашего приложения или полностью остановить его, если это необходимо, чтобы освободить память для важных задач, как описано в разделе Обзор управления памятью . Чтобы еще больше сбалансировать системную память и избежать необходимости остановки процесса вашего приложения, вы можете реализовать интерфейс ComponentCallbacks2 в своих классах Activity . Предоставленный метод обратного вызова onTrimMemory() позволяет вашему приложению прослушивать события, связанные с памятью, когда ваше приложение находится либо на переднем плане, либо в фоновом режиме. Затем он позволяет вашему приложению выпускать объекты в ответ на жизненный цикл приложения или системные события, которые указывают, что системе необходимо освободить память.

Вы можете реализовать обратный вызов onTrimMemory() для реагирования на различные события, связанные с памятью, как показано в следующем примере:

Котлин

import android.content.ComponentCallbacks2
// Other import statements.

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    // Other activity code.

    /**
     * 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) {

        // Determine which lifecycle or system event is raised.
        when (level) {

            ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */
            }

            else -> {
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
            }
        }
    }
}

Ява

import android.content.ComponentCallbacks2;
// Other import statements.

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    public void onTrimMemory(int level) {

        // Determine which lifecycle or system event is raised.
        switch (level) {

            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */

                break;

            default:
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
                break;
        }
    }
}

Проверьте, сколько памяти вам нужно

Чтобы разрешить несколько запущенных процессов, Android устанавливает жесткое ограничение на размер кучи, выделяемой для каждого приложения. Точный предел размера кучи варьируется в зависимости от устройства в зависимости от общего объема оперативной памяти, доступной устройству. Если ваше приложение достигает емкости кучи и пытается выделить больше памяти, система выдает OutOfMemoryError .

Чтобы избежать нехватки памяти, вы можете запросить систему, чтобы определить, сколько места в куче доступно на текущем устройстве. Вы можете запросить эту цифру у системы, вызвав getMemoryInfo() . Возвращает объект ActivityManager.MemoryInfo , который предоставляет информацию о текущем состоянии памяти устройства, включая доступную память, общий объем памяти и порог памяти — уровень памяти, при котором система начинает останавливать процессы. Объект ActivityManager.MemoryInfo также предоставляет lowMemory — простое логическое значение, сообщающее, не хватает ли устройству памяти.

В следующем фрагменте кода показано, как использовать метод getMemoryInfo() в вашем приложении.

Котлин

fun doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    if (!getAvailableMemory().lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return ActivityManager.MemoryInfo().also { memoryInfo ->
        activityManager.getMemoryInfo(memoryInfo)
    }
}

Ява

public void doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}

Используйте более эффективные конструкции кода с использованием памяти.

Некоторые функции Android, классы Java и конструкции кода используют больше памяти, чем другие. Вы можете минимизировать объем памяти, используемый вашим приложением, выбрав в своем коде более эффективные альтернативы.

Пользуйтесь услугами экономно

Мы настоятельно рекомендуем вам не оставлять службы включенными, когда в этом нет необходимости. Оставление ненужных служб запущенными — одна из худших ошибок управления памятью, которую может совершить Android-приложение. Если вашему приложению нужна служба для работы в фоновом режиме, не оставляйте ее включенной, если только ей не нужно запустить задание. Остановите службу, когда она завершит свою задачу. В противном случае вы можете вызвать утечку памяти.

Когда вы запускаете службу, система предпочитает продолжать процесс этой службы. Такое поведение делает процессы обслуживания очень дорогими, поскольку оперативная память, используемая службой, остается недоступной для других процессов. Это уменьшает количество кэшированных процессов, которые система может хранить в кеше LRU, что делает переключение приложений менее эффективным. Это может даже привести к сбою в системе, когда памяти не хватает и система не может поддерживать достаточное количество процессов для размещения всех запущенных в данный момент служб.

Как правило, избегайте использования постоянных служб из-за постоянных требований, которые они предъявляют к доступной памяти. Вместо этого мы рекомендуем использовать альтернативную реализацию, например WorkManager . Дополнительные сведения о том, как использовать WorkManager для планирования фоновых процессов, см. в разделе Постоянная работа .

Используйте оптимизированные контейнеры данных

Некоторые классы языка программирования не оптимизированы для использования на мобильных устройствах. Например, универсальная реализация HashMap может быть неэффективной с точки зрения использования памяти, поскольку для каждого сопоставления требуется отдельный объект записи.

Платформа Android включает несколько оптимизированных контейнеров данных, включая SparseArray , SparseBooleanArray и LongSparseArray . Например, классы SparseArray более эффективны, поскольку они позволяют системе избежать необходимости автоматически упаковывать ключ, а иногда и значение, что создает еще один или два объекта для каждой записи.

При необходимости вы всегда можете переключиться на необработанные массивы для более компактной структуры данных.

Будьте осторожны с абстракциями кода.

Разработчики часто используют абстракции как хорошую практику программирования, поскольку они могут улучшить гибкость и поддержку кода. Однако абстракции обходятся значительно дороже, поскольку обычно требуют больше кода, который необходимо выполнить, а также больше времени и оперативной памяти для отображения кода в памяти. Если ваши абстракции не приносят существенной пользы, избегайте их.

Используйте облегченные protobufs для сериализованных данных

Буферы протоколов (protobufs) — это независимый от языка и платформы расширяемый механизм, разработанный Google для сериализации структурированных данных. Он похож на XML, но меньше по размеру, быстрее и проще. Если вы используете protobufs для своих данных, всегда используйте облегченные protobufs в своем клиентском коде. Обычные protobufs генерируют чрезвычайно подробный код, который может вызвать множество проблем в вашем приложении, например, увеличение использования оперативной памяти, значительное увеличение размера APK и замедление выполнения.

Дополнительную информацию см. в файле readme protobuf .

Избегайте перегрузки памяти

События сборки мусора не влияют на производительность вашего приложения. Однако многие события сборки мусора, происходящие в течение короткого периода времени, могут быстро разрядить батарею, а также незначительно увеличить время настройки кадров из-за необходимого взаимодействия между сборщиком мусора и потоками приложения. Чем больше времени система тратит на сбор мусора, тем быстрее разряжается аккумулятор.

Часто переполнение памяти может привести к возникновению большого количества событий сборки мусора. На практике отток памяти описывает количество выделенных временных объектов, возникающих за определенный промежуток времени.

Например, вы можете разместить несколько временных объектов внутри цикла for . Или вы можете создать новые объекты Paint или Bitmap внутри функции onDraw() представления. В обоих случаях приложение быстро создает множество объектов на большой громкости. Они могут быстро занять всю доступную память молодого поколения, вызывая событие сборки мусора.

Используйте профилировщик памяти , чтобы найти места в вашем коде, где наблюдается высокий отток памяти, прежде чем вы сможете их исправить.

После того как вы определите проблемные области в своем коде, постарайтесь уменьшить количество выделений в областях, критически важных для производительности. Подумайте о том, чтобы вывести вещи из внутренних циклов или, возможно, переместить их в структуру распределения на основе фабрики .

Вы также можете оценить, принесут ли пулы объектов пользу этому варианту использования. В пуле объектов вместо того, чтобы бросать экземпляр объекта на пол, вы помещаете его в пул после того, как он больше не нужен. В следующий раз, когда понадобится экземпляр объекта этого типа, вы можете получить его из пула, а не выделять.

Тщательно оцените производительность, чтобы определить, подходит ли пул объектов в конкретной ситуации. Бывают случаи, когда пулы объектов могут снизить производительность. Несмотря на то, что пулы избегают выделения ресурсов, они вносят другие накладные расходы. Например, обслуживание пула обычно включает в себя синхронизацию, которая требует значительных затрат. Кроме того, очистка экземпляра объекта в пуле во избежание утечек памяти во время выпуска, а затем его инициализация во время получения могут иметь ненулевые издержки.

Сохранение в пуле большего количества экземпляров объектов, чем необходимо, также создает нагрузку на сборку мусора. Хотя пулы объектов уменьшают количество вызовов сборки мусора, они в конечном итоге увеличивают объем работы, необходимый для каждого вызова, поскольку он пропорционален количеству живых (доступных) байтов.

Удалите ресурсоемкие ресурсы и библиотеки.

Некоторые ресурсы и библиотеки в вашем коде могут потреблять память без вашего ведома. Общий размер вашего приложения, включая сторонние библиотеки или встроенные ресурсы, может влиять на объем памяти, потребляемый вашим приложением. Вы можете улучшить потребление памяти вашим приложением, удалив из своего кода избыточные, ненужные или раздутые компоненты, а также ресурсы и библиотеки.

Уменьшить общий размер APK

Вы можете значительно сократить использование памяти вашим приложением, уменьшив общий размер вашего приложения. Размер растрового изображения, ресурсы, кадры анимации и сторонние библиотеки — все это может влиять на размер вашего приложения. Android Studio и Android SDK предоставляют множество инструментов, которые помогут уменьшить размер ваших ресурсов и внешних зависимостей. Эти инструменты поддерживают современные методы сжатия кода, такие как компиляция R8 .

Дополнительные сведения об уменьшении общего размера приложения см. в разделе Уменьшение размера приложения .

Используйте Hilt или Dagger 2 для внедрения зависимостей.

Платформы внедрения зависимостей могут упростить написанный вами код и предоставить адаптивную среду, полезную для тестирования и других изменений конфигурации.

Если вы собираетесь использовать в своем приложении платформу внедрения зависимостей, рассмотрите возможность использования Hilt или Dagger . Hilt — это библиотека внедрения зависимостей для Android, работающая поверх Dagger. Dagger не использует отражение для сканирования кода вашего приложения. Вы можете использовать статическую реализацию времени компиляции Dagger в приложениях Android без ненужных затрат времени выполнения или использования памяти.

Другие платформы внедрения зависимостей, использующие отражение, инициализируют процессы путем сканирования вашего кода на наличие аннотаций. Этот процесс может потребовать значительно больше циклов ЦП и оперативной памяти и может вызвать заметную задержку при запуске приложения.

Будьте осторожны при использовании внешних библиотек.

Код внешней библиотеки часто не пишется для мобильных сред и может быть неэффективным для работы с мобильным клиентом. Когда вы используете внешнюю библиотеку, вам может потребоваться оптимизировать ее для мобильных устройств. Запланируйте эту работу заранее и проанализируйте библиотеку с точки зрения размера кода и объема оперативной памяти, прежде чем использовать ее.

Даже некоторые библиотеки, оптимизированные для мобильных устройств, могут вызывать проблемы из-за различий в реализации. Например, одна библиотека может использовать облегченные protobufs, а другая — микроprotobufs, в результате чего в вашем приложении появятся две разные реализации protobuf. Это может произойти с различными реализациями ведения журналов, аналитики, платформ загрузки изображений, кэширования и многих других вещей, которых вы не ожидаете.

Хотя ProGuard может помочь удалить API и ресурсы с помощью правильных флагов, он не может удалить большие внутренние зависимости библиотеки. Функции, которые вам нужны в этих библиотеках, могут потребовать зависимостей более низкого уровня. Это становится особенно проблематичным, когда вы используете подкласс Activity из библиотеки, который может иметь широкий спектр зависимостей, когда библиотеки используют отражение, что является обычным явлением и требует ручной настройки ProGuard, чтобы оно работало.

Избегайте использования общей библиотеки только для одной или двух функций из десятков. Не используйте большой объем кода и накладных расходов, которые вы не используете. Когда вы решаете, использовать ли библиотеку, ищите реализацию, которая полностью соответствует тому, что вам нужно. В противном случае вы можете решить создать свою собственную реализацию.