Примечание. Существует несколько библиотек, которые следуют рекомендациям по загрузке изображений. Вы можете использовать эти библиотеки в своем приложении для наиболее оптимизированной загрузки изображений. Мы рекомендуем библиотеку Glide , которая максимально быстро и плавно загружает и отображает изображения. Другие популярные библиотеки загрузки изображений включают Picasso от Square, Coil от Instacart и Fresco от Facebook. Эти библиотеки упрощают большинство сложных задач, связанных с растровыми изображениями и другими типами изображений на Android.
Изображения бывают всех форм и размеров. Во многих случаях они больше, чем требуется для типичного пользовательского интерфейса (UI) приложения. Например, системное приложение «Галерея» отображает фотографии, сделанные с помощью камеры вашего устройства Android, разрешение которых обычно намного выше, чем плотность экрана вашего устройства.
Учитывая, что вы работаете с ограниченной памятью, в идеале вам нужно загружать в память только версию с более низким разрешением. Версия с более низким разрешением должна соответствовать размеру компонента пользовательского интерфейса, который ее отображает. Изображение с более высоким разрешением не дает никакой видимой выгоды, но все равно занимает драгоценную память и требует дополнительных затрат производительности из-за дополнительного масштабирования на лету.
В этом уроке вы узнаете, как декодировать большие растровые изображения, не превышая предел памяти для каждого приложения, загрузив в память меньшую версию с субдискретизацией.
Чтение размеров и типа растрового изображения
Класс BitmapFactory
предоставляет несколько методов декодирования ( decodeByteArray()
, decodeFile()
, decodeResource()
и т. д.) для создания Bitmap
из различных источников. Выберите наиболее подходящий метод декодирования в зависимости от источника данных изображения. Эти методы пытаются выделить память для созданного растрового изображения и поэтому могут легко привести к исключению OutOfMemory
. Каждый тип метода декодирования имеет дополнительные сигнатуры, которые позволяют указать параметры декодирования через класс BitmapFactory.Options
. Установка для свойства inJustDecodeBounds
значения true
во время декодирования позволяет избежать выделения памяти, возвращая значение null
для растрового объекта, но устанавливая outWidth
, outHeight
и outMimeType
. Этот метод позволяет вам считывать размеры и тип данных изображения до построения (и выделения памяти) растрового изображения.
Котлин
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } BitmapFactory.decodeResource(resources, R.id.myimage, options) val imageHeight: Int = options.outHeight val imageWidth: Int = options.outWidth val imageType: String = options.outMimeType
Ява
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType;
Чтобы избежать исключений java.lang.OutOfMemory
, проверьте размеры растрового изображения перед его декодированием, если только вы не абсолютно уверены, что источник предоставит вам данные изображения предсказуемого размера, которые удобно помещаются в доступную память.
Загрузите уменьшенную версию в память
Теперь, когда размеры изображения известны, их можно использовать, чтобы решить, следует ли загружать полное изображение в память или вместо этого следует загружать версию с субдискретизацией. Вот некоторые факторы, которые следует учитывать:
- Примерное использование памяти при загрузке полного изображения в память.
- Объем памяти, который вы готовы выделить для загрузки этого образа, учитывая любые другие требования к памяти вашего приложения.
- Размеры целевого
ImageView
или компонента пользовательского интерфейса, в который должно быть загружено изображение. - Размер экрана и плотность текущего устройства.
Например, не стоит загружать в память изображение размером 1024x768 пикселей, если оно в конечном итоге будет отображаться в миниатюре размером 128x96 пикселей в ImageView
.
Чтобы сообщить декодеру о субдискретизации изображения и загрузке уменьшенной версии в память, установите для inSampleSize
значение true
в объекте BitmapFactory.Options
. Например, изображение с разрешением 2048x1536, декодированное с помощью inSampleSize
, равного 4, создает растровое изображение размером примерно 512x384. При загрузке этого изображения в память используется 0,75 МБ, а не 12 МБ для полного изображения (при условии, что конфигурация растрового изображения ARGB_8888
). Вот метод расчета значения размера выборки, которое представляет собой степень двойки на основе целевой ширины и высоты:
Котлин
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { // Raw height and width of image val (height: Int, width: Int) = options.run { outHeight to outWidth } var inSampleSize = 1 if (height > reqHeight || width > reqWidth) { val halfHeight: Int = height / 2 val halfWidth: Int = width / 2 // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) { inSampleSize *= 2 } } return inSampleSize }
Ява
public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; }
Примечание. Значение степени двойки рассчитывается, поскольку декодер использует окончательное значение путем округления до ближайшей степени двойки, как указано в документации inSampleSize
.
Чтобы использовать этот метод, сначала декодируйте, установив для inJustDecodeBounds
значение true
, передайте параметры, а затем снова декодируйте, используя новое значение inSampleSize
и значение inJustDecodeBounds
, установленное в false
:
Котлин
fun decodeSampledBitmapFromResource( res: Resources, resId: Int, reqWidth: Int, reqHeight: Int ): Bitmap { // First decode with inJustDecodeBounds=true to check dimensions return BitmapFactory.Options().run { inJustDecodeBounds = true BitmapFactory.decodeResource(res, resId, this) // Calculate inSampleSize inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight) // Decode bitmap with inSampleSize set inJustDecodeBounds = false BitmapFactory.decodeResource(res, resId, this) } }
Ява
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }
Этот метод позволяет легко загрузить растровое изображение произвольно большого размера в ImageView
, который отображает миниатюру размером 100x100 пикселей, как показано в следующем примере кода:
Котлин
imageView.setImageBitmap( decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100) )
Ява
imageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
Вы можете выполнить аналогичный процесс для декодирования растровых изображений из других источников, подставив при необходимости соответствующий метод BitmapFactory.decode*
.