Оптимизация использования памяти

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

Высокое потребление памяти может привести к проблемам с работой приложений и системы, в том числе:

  • Само приложение может работать медленно или с задержками, а в худшем случае – быть закрыто.
  • Системные сервисы, видимые пользователю (регулятор громкости, панель настроек изображения, голосовой помощник и т. д.), начинают работать с задержками или перестают работать вовсе.
  • Демон low memory killer (LMK) может реагировать на нехватку памяти, завершая наименее важные процессы. Затем эти компоненты могут перезапуститься, что приведет к дальнейшему конфликту ресурсов, который может напрямую повлиять на работу активного приложения.
  • Переход к панели запуска может значительно задержаться, и активное приложение будет выглядеть неотвечающим, пока переход не завершится.
  • Система может начать использовать прямое освобождение, временно приостанавливая выполнение потока в ожидании выделения памяти. Это может произойти с любой цепочкой, например с основной или связанной с кодеком, и привести к пропуску кадров аудио и видео, а также к сбоям в работе интерфейса.

Особенности использования памяти на телевизорах

У телевизоров обычно гораздо меньше памяти, чем у телефонов или планшетов. Например, на телевизоре может быть 1 ГБ ОЗУ и разрешение видео 1080p. При этом большинство приложений для телевизоров имеют похожие функции, а значит, и похожие проблемы с реализацией. Эти две ситуации создают проблемы, которые не встречаются в других типах устройств и приложений:

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

Телевизоры

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

При выборе телевизора учитывайте следующие характеристики:

  • Память устройства. Объем оперативной памяти (ОЗУ), установленной на устройстве.
  • Разрешение интерфейса устройства. Разрешение, которое устройство использует для отрисовки интерфейса ОС и приложений. Обычно оно ниже разрешения видео.
  • Разрешение видео. Максимальное разрешение, в котором устройство может воспроизводить видео.

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

Сводка по телевизорам

Память устройства Разрешение видео на устройстве Разрешение интерфейса устройства isLowRamDevice()
1 ГБ 1080p 720p Да
1,5 ГБ 2160p 1080p Да
≥ 1,5 ГБ 1080p 720p или 1080p Нет*
≥ 2 ГБ 2160p 1080p Нет*

Телевизоры с небольшим объемом ОЗУ

Эти устройства ограничены в памяти и будут сообщать ActivityManager.isLowRamDevice() как истинное значение. Приложения, работающие на телевизорах с небольшим объемом ОЗУ, должны реализовывать дополнительные меры контроля памяти.

К этой категории относятся устройства со следующими характеристиками:

  • Устройства с 1 ГБ ОЗУ: 1 ГБ ОЗУ, разрешение интерфейса 720p/HD (1280x720), разрешение видео 1080p/FullHD (1920x1080).
  • Устройства с 1,5 ГБ ОЗУ: 1,5 ГБ ОЗУ, разрешение интерфейса 1080p/FullHD (1920x1080), разрешение видео 2160p/UltraHD/4K (3840x2160).
  • Другие ситуации, в которых производитель устройства задал флаг ActivityManager.isLowRamDevice() из-за дополнительных ограничений памяти.

обычные телевизоры;

На таких устройствах не возникает значительной нехватки памяти. Мы считаем, что эти устройства обладают следующими характеристиками:

  • не менее 1,5 ГБ ОЗУ, интерфейс 720p или 1080p и разрешение видео 1080p;
  • не менее 2 ГБ ОЗУ, интерфейс 1080p и разрешение видео 1080p или 2160p;

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

Целевые показатели памяти на телевизорах с небольшим объемом ОЗУ

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

профилировщик памяти;

В разделе Как учитывается память приводится подробное объяснение того, как рассчитываются показатели памяти. Пороговые значения для приложений на ТВ будут определяться на основе трех категорий памяти:

  • Анонимная память + файл подкачки. Состоит из памяти, выделенной для Java, нативных библиотек и стека в Android Studio.
  • Графика. Данные, полученные непосредственно из инструмента профилирования. Обычно состоит из текстур графики.
  • Файл. В Android Studio относится к категориям "Код" и "Другое".

С учетом этих определений в таблице ниже указано максимальное значение, которое должна использовать каждая группа памяти:

Тип памяти Purpose Целевые показатели использования (1 ГБ)
Анонимная память + файл подкачки (Java + Native + Stack) Используется для распределения памяти, буферов мультимедиа, переменных и других задач, требующих большого объема памяти. < 160 МБ
Графика Используется графическим процессором для текстур и буферов, связанных с дисплеем. 30–40 МБ
Файл Используется для страниц кода и файлов в памяти. 60–80 МБ
.

Максимальный общий объем памяти (анонимная память + файл подкачки + графическая память + память для файлов) не должен превышать следующие значения:

  • 280 МБ общего использования памяти (анонимные данные об RSS и swap-пространстве + графика + файлы) для устройств с низким объемом ОЗУ (1 ГБ).

Настоятельно рекомендуется не превышать следующие значения:

  • 200 МБ использования памяти (анонимный RSS + файл подкачки + графика).

Память файлов

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

  • Как правило, памятью файлов хорошо управляет ОС.
  • В настоящее время мы не считаем, что это основная причина нехватки памяти.

Однако при работе с памятью файлов в целом:

  • Не добавляйте в сборку неиспользуемые библиотеки. По возможности используйте не полные библиотеки, а их небольшие подмножества.
  • Не держите большие файлы открытыми в памяти и закрывайте их, как только они вам больше не нужны.
  • Уменьшите размер скомпилированного кода для классов Java и Kotlin. Подробнее о том, как это сделать, рассказывается в руководстве Как уменьшить размер приложения, запутать код и оптимизировать его.

Рекомендации по выбору телевизора

В этом разделе приведены рекомендации по оптимизации использования памяти на устройствах Android TV.

Память графического процессора

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

  • Не загружайте изображения с разрешением выше, чем разрешение интерфейса устройства. Например, если разрешение экрана устройства составляет 720p, изображения с разрешением 1080p должны быть уменьшены до 720p.
  • По возможности используйте аппаратные растровые изображения.
    • В библиотеках, таких как Glide, включите функцию Downsampler.ALLOW_HARDWARE_CONFIG, которая по умолчанию отключена. Включив эту функцию, вы избежите дублирования растровых изображений, которые в противном случае будут храниться и в графической, и в анонимной памяти.
  • Избегайте промежуточных и повторных отрисовок
    • Их можно выявить с помощью Android GPU Inspector:
    • В разделе "Текстуры" ищите изображения, которые являются этапами финальной визуализации, а не просто элементами, из которых она состоит. Обычно это так называемая промежуточная визуализация.
    • В приложениях Android SDK часто можно удалить эти элементы, используя флаг макета forceHasOverlappedRendering:false для отключения промежуточных отрисовок для этого макета.
    • Рекомендуем ознакомиться с разделом Избегайте перекрывающихся отрисовок.
  • Старайтесь не загружать замещающие изображения. Используйте @android:color/ или @color для замещающих текстур.
  • Не объединяйте несколько изображений на устройстве, если это можно сделать в офлайн-режиме. Предпочитать загрузку отдельных изображений, а не составление изображений из скачанных файлов.
  • Чтобы лучше работать с растровыми изображениями, следуйте инструкциям из руководства Работа с растровыми изображениями.

Память Anon+Swap

Anon+Swap состоит из нативных, Java- и стековых выделений в профилировщике памяти Android Studio. Используйте ActivityManager.isLowMemoryDevice(), чтобы проверить, ограничена ли память устройства, и адаптируйте приложение к этой ситуации, следуя приведенным ниже рекомендациям.

  • Медиа
    • Укажите переменный размер буферов мультимедиа в зависимости от объема ОЗУ устройства и разрешения воспроизведения видео. Это должно соответствовать одной минуте воспроизведения видео:
      1. 40–60 МБ на 1 ГБ / 1080p
      2. 60–80 МБ для 1,5 ГБ / 1080p
      3. 80–100 МБ для 1,5 ГБ / 2160p
      4. 100–120 МБ для 2 ГБ / 2160p
    • Освобождение памяти, выделенной для медиафайлов, при переходе к следующей серии, чтобы предотвратить увеличение общего объема анонимной памяти.
    • Немедленно освобождайте и останавливайте медиаресурсы, когда приложение останавливается. Используйте обратные вызовы жизненного цикла, чтобы управлять аудио- и видеоресурсами. Если ваше приложение не относится к аудиоприложениям, остановите воспроизведение, когда в ваших действиях произойдет onStop, сохраните все выполненные действия и освободите ресурсы. Чтобы запланировать работу на более поздний срок, ознакомьтесь с разделом Задания и будильники.
    • Обращайте внимание на память буфера при перемотке видео. Разработчики часто выделяют дополнительно 15–60 секунд контента при перемотке, чтобы видео было готово для пользователя, но это создает дополнительную нагрузку на память. В общем случае не следует использовать буфер более пяти секунд, пока пользователь не выберет новую позицию в видео. Если вам необходимо предварительно буферизировать дополнительное время при перемотке, убедитесь, что:
      • Заранее выделите буфер для перемотки и используйте его повторно.
      • Размер буфера не должен превышать 15–25 МБ (в зависимости от объема памяти устройства).
  • Распределение:
    • Используйте рекомендации по использованию памяти для графики, чтобы не дублировать изображения в анонимной памяти.
      • Изображения часто занимают больше всего места в памяти, поэтому их дублирование может сильно нагружать устройство. Это особенно заметно при просмотре большого количества изображений.
    • Освобождайте выделенную память, удаляя ссылки на нее при переходе между экранами. Убедитесь, что не осталось ссылок на растровые изображения и объекты.
  • Библиотеки:
    • Распределение памяти профиля из библиотек при добавлении новых библиотек, поскольку они могут загружать дополнительные библиотеки, которые также могут распределять память и создавать привязки.
  • Общение в неформальной обстановке
    • Не блокировать сетевые вызовы при запуске приложения. Они замедляют запуск приложения и создают дополнительную нагрузку на память, которая и так ограничена при загрузке приложения. Сначала покажите экран загрузки или заставку, а затем выполняйте сетевые запросы.

Привязки

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

Типичные привязки и рекомендации:

  • Play Integrity API используется для проверки целостности устройства.
    • Проверять целостность данных на устройстве после экрана загрузки и перед воспроизведением медиаконтента.
    • Перед воспроизведением контента освободите ссылки на PlayIntegrity StandardIntegrityManager.
  • Библиотека Play Платежей используется для управления подписками и покупками через Google Play.
    • Инициализируйте библиотеку после экрана загрузки и выполните все операции, связанные с оплатой, до воспроизведения медиаконтента.
    • Используйте BillingClient.endConnection() после того, как закончите работу с библиотекой, и всегда перед воспроизведением видео или медиафайлов.
    • Используйте BillingClient.isReady() и BillingClient.getConnectionState(), чтобы проверить, не был ли отключен сервис, если нужно повторно выполнить какие-либо платежные операции, а затем снова выполните BillingClient.endConnection().
  • GMS FontsProvider
    • На устройствах с небольшим объемом ОЗУ лучше использовать отдельные шрифты, а не поставщика шрифтов, поскольку скачивание шрифтов требует много ресурсов, а FontsProvider будет привязывать сервисы для этого.
  • Библиотека Google Ассистента. Иногда используется для поиска и поиска в приложении. Если возможно, замените эту библиотеку.
    • Для приложений leanback используйте функцию преобразования текста в речь Gboard или библиотеку androidx.leanback.
    • Для приложений, созданных с помощью Compose:
      • Используйте функцию преобразования текста в речь Gboard для голосового поиска.
    • Реализуйте функцию Что посмотреть дальше, чтобы пользователи могли находить медиаконтент в вашем приложении.

Активные службы

Активные службы – это особый тип служб, связанных с уведомлениями. Это уведомление показывается в панели уведомлений на телефонах и планшетах, но на телевизорах такой панели нет. Активные службы могут быть полезны, поскольку они продолжают работать, когда приложение находится в фоновом режиме. Однако приложения для телевизоров должны соответствовать следующим требованиям:

В Android TV и Google TV активные службы могут работать после того, как пользователь покинет приложение, только в следующих случаях:

  • Для аудиоприложений Активные службы могут продолжать работу после того, как пользователь покинет приложение, чтобы воспроизводить звуковую дорожку. Сервис должен быть остановлен сразу после окончания воспроизведения аудио.
  • Для любого другого приложения: все активные службы должны быть остановлены, когда пользователь покидает приложение, поскольку нет уведомления, которое сообщало бы пользователю, что приложение по-прежнему работает и потребляет ресурсы.
  • Для фоновых задач, например обновления рекомендаций или списка "Смотреть дальше", используйте WorkManager.

Работа и будильники

WorkManager – это современный API Android для планирования фоновых повторяющихся задач. WorkManager будет использовать новый JobScheduler, если он доступен (SDK 23 и более поздних версий), и старый AlarmManager, если нет. Чтобы запланированные задания на телевизоре выполнялись корректно, следуйте приведенным ниже рекомендациям.

  • Не используйте API AlarmManager на устройствах с SDK 23 и более поздних версий, особенно AlarmManager.set(), AlarmManager.setExact() и похожие методы, поскольку они не позволяют системе определять подходящее время для выполнения задач (например, когда устройство не используется).
  • На устройствах с небольшим объемом ОЗУ запускайте задачи только в случае крайней необходимости. При необходимости используйте WorkManager WorkRequest только для обновления рекомендаций после воспроизведения и старайтесь делать это, пока приложение ещё открыто.
  • Определите WorkManager Constraints, чтобы система выполняла ваши задачи в подходящее время:

Kotlin

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()

Java

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()
  • Если вам нужно регулярно выполнять задачи (например, чтобы обновлять раздел Что посмотреть дальше на основе действий пользователя в вашем приложении на другом устройстве), следите за тем, чтобы потребление памяти задачей не превышало 30 МБ.

Общие рекомендации по памяти

Ниже приведены общие рекомендации по разработке приложений для Android.

  • Минимизируйте выделение объектов, оптимизируйте повторное использование объектов и своевременно освобождайте неиспользуемые объекты.
    • Не сохраняйте ссылки на объекты, особенно на растровые изображения.
    • Не используйте System.gc() и прямые вызовы для освобождения памяти, поскольку они мешают системному процессу управления памятью. Например, на устройствах с zRAM принудительный вызов gc() может временно увеличить использование памяти из-за сжатия и распаковки.
    • Используйте LazyList, как показано в браузере каталога в Compose или RecyclerView в устаревшем наборе инструментов Leanback UI, чтобы повторно использовать представления и не создавать заново элементы списка.
    • Кешируйте локально элементы, полученные от внешних поставщиков контента, которые вряд ли изменятся, и задайте интервалы обновления, которые не позволят выделить дополнительную внешнюю память.
  • Проверьте, нет ли утечек памяти.
    • Обратите внимание на типичные случаи утечки памяти, например ссылки в анонимных потоках, перераспределение видео буферов, которые никогда не освобождаются, и другие подобные ситуации.
    • Используйте дамп кучи, чтобы отлаживать утечки памяти.
  • Создайте базовые профили, чтобы свести к минимуму количество компиляций JIT при холодном запуске приложения.

Что такое прямое освобождение памяти

Когда приложение для Android TV запрашивает память и система перегружена, ядро Linux, на котором основана ОС Android, может использовать прямое освобождение памяти.

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

Это может привести к заметным паузам или временным зависаниям в удобстве использования, поскольку система приостанавливает выделение потоков до тех пор, пока не будет доступно достаточно памяти. В этом смысле выделение потоков не ограничивается вызовами кода приложения, такими как malloc(). Например, память должна быть выделена для страницы в кодовых страницах.

Сводка по инструментам

  • Используйте профилировщик памяти Android Studio, чтобы проверить потребление памяти во время использования.
    • Используйте heapdump, чтобы проверить выделение определенных объектов и растровых изображений.
    • Используйте профилировщик нативной памяти, чтобы проверить выделение памяти, не относящейся к Java или Kotlin.
  • Используйте Android GPU Inspector, чтобы проверить распределение графических ресурсов.