Оптимизация растровых изображений

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

Используйте библиотеки для загрузки изображений

Чтобы повысить эффективность приложения, используйте библиотеки загрузки изображений, например Coil (для проектов на Kotlin) или Glide (для проектов на Java). Такие библиотеки снижают использование памяти, например кешируя изображения, уменьшая разрешение графики при необходимости и повторно используя графические объекты.

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

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

Клиентское уменьшение частоты дискретизации

Библиотеки загрузки изображений, такие как Coil и Glide, автоматически уменьшают разрешение. Вы можете настроить стратегии субдискретизации с помощью ImageLoader (для Coil) или DownsampleStrategy (для Glide). Если вы управляете растровыми изображениями вручную, то можете использовать inSampleSize, чтобы декодировать уменьшенную версию. Чтобы сделать это безопасно, сначала задайте для inJustDecodeBounds значение true, чтобы считать размеры изображения без выделения памяти, рассчитайте размер выборки, задайте для inSampleSize это значение, задайте для inJustDecodeBounds значение false, а затем декодируйте изображение.

Предпочитать изменение размера на стороне сервера

По возможности запрашивайте изображения нужного размера непосредственно с бэкенд-сервера. Это позволяет снизить нагрузку на сеть и уменьшить размер кеша диска, а также сократить использование памяти, поскольку устройству не нужно будет изменять размер изображений.

Вы можете настроить библиотеки так, чтобы они динамически добавляли целевой размер области просмотра в URL изображения. Например, в Coil это можно сделать с помощью пользовательских перехватчиков, а в Glide – с помощью пользовательских загрузчиков моделей (таких как BaseGlideUrlLoader).

Не используйте макеты без ограничений по размеру

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

Не используйте wrapContentSize и не оставляйте размеры неограниченными в composable-функциях, которые загружают удаленные изображения. Если эти библиотеки не могут определить целевые границы, они загружают исходное полноразмерное изображение. Это может привести к загрузке изображения, размер которого значительно больше необходимого, что увеличивает использование памяти и задержку.

Вместо этого задайте явные размеры для composable-функции изображения (например, с помощью Modifier.size) или определите соотношение сторон. Это позволяет механизму макета заранее рассчитать точный целевой пиксель, который загрузчик изображений затем может использовать для запроса и декодирования ресурса правильного размера.

Добавьте альтернативные ресурсы для экранов разных размеров

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

Не применяйте отступы напрямую

Иногда к изображению нужно добавить поля. Например, вы можете добавить прозрачную рамку, чтобы изображение было в формате Letterbox. В таких случаях не добавляйте поля непосредственно к изображению, изменяя его размеры. Вместо этого оставьте размеры изображения как есть и настройте его положение на экране с помощью InsetDrawable. Вы также можете добавить отступы в Composable или View, содержащие изображение.

Как выбрать подходящий формат пикселя

Выберите формат пикселей, который обеспечит оптимальное соотношение качества и объема памяти. Используйте RGB_565, если вам не нужна прозрачность. Этот формат занимает вдвое меньше памяти, чем формат по умолчанию ARGB_8888.

В Glide это можно настроить с помощью DecodeFormat. В Coil можно использовать свойство bitmapConfig.

По возможности используйте векторы

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

По возможности освобождайте и повторно используйте битовые карты

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

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

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

Другие полезные советы

В этом разделе перечислены другие способы повышения производительности приложения при обработке графики.

Не упаковывайте большие изображения в файл AAB или APK

Одна из основных причин большого размера скачиваемого приложения – графические элементы, упакованные в файл AAB или APK. Используйте инструмент APK Analyzer, чтобы убедиться, что вы не упаковываете графические файлы большего размера, чем требуется. Уменьшите размер изображений или разместите их на сервере и скачивайте только при необходимости.

Поиск лишних растровых изображений

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

При использовании ImageBitmap вызовите prepareToDraw перед рисованием.

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

Рекомендуется передавать Int DrawableRes или URL в качестве параметров в composable-функцию вместо Painter.

Из-за сложностей, связанных с обработкой изображений (например, написание функции equals для Bitmaps потребует больших вычислительных ресурсов), API Painter явно не помечен как стабильный с помощью аннотации @Stable. Нестабильные классы могут привести к ненужным рекомпозициям, поскольку компилятору сложно определить, изменились ли данные.

Поэтому мы рекомендуем передавать в composable-функцию URL или идентификатор прорисовываемого ресурса, а не Painter.

// Prefer this:
@Composable
fun MyImage(url: String) {

}
// Over this:
@Composable
fun MyImage(painter: Painter) {

}