Примечание. Существует несколько библиотек, которые следуют рекомендациям по загрузке изображений. Вы можете использовать эти библиотеки в своем приложении, чтобы загружать изображения наиболее оптимальным способом. Мы рекомендуем использовать библиотеку Glide, которая загружает и показывает изображения максимально быстро и плавно. Другие популярные библиотеки для загрузки изображений: Picasso от Square, Coil от Instacart и Fresco от Facebook. Эти библиотеки упрощают большинство сложных задач, связанных с растровыми и другими типами изображений на Android.
Изображения бывают разных форм и размеров. Во многих случаях они больше, чем требуется для типичного пользовательского интерфейса приложения. Например, в системном приложении "Галерея" показываются фотографии, сделанные камерой устройства Android. Обычно их разрешение намного выше, чем плотность пикселей экрана.
Поскольку объем памяти ограничен, лучше загружать в нее версию с более низким разрешением. Размер версии с более низким разрешением должен соответствовать размеру компонента пользовательского интерфейса, в котором она отображается. Изображение с более высоким разрешением не дает видимых преимуществ, но занимает драгоценную память и приводит к дополнительным затратам на производительность из-за дополнительного масштабирования на лету.
В этом уроке вы узнаете, как декодировать большие растровые изображения, не превышая лимит памяти для каждого приложения, загружая в память уменьшенную версию с субдискретизацией.
Чтение размеров и типа растрового изображения
Класс BitmapFactory предоставляет несколько методов декодирования (decodeByteArray(), decodeFile(), decodeResource() и т. д.) для создания Bitmap из различных источников. Выберите наиболее подходящий метод декодирования в зависимости от источника данных изображения. Эти методы пытаются выделить память для созданной битовой карты и поэтому могут легко привести к исключению OutOfMemory. У каждого метода декодирования есть дополнительные сигнатуры, которые позволяют задавать параметры декодирования с помощью класса BitmapFactory.Options. Если при декодировании задать для свойства inJustDecodeBounds значение true, то выделение памяти не будет выполняться, а для объекта битовой карты будет возвращено значение null. При этом будут заданы значения outWidth, outHeight и outMimeType. Этот метод позволяет считывать размеры и тип данных изображения до построения (и выделения памяти) растрового изображения.
Kotlin
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
Java
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или компонента интерфейса, в который загружается изображение. - Размер и плотность экрана текущего устройства.
Например, не стоит загружать в память изображение размером 1024 x 768 пикселей, если в конечном итоге оно будет отображаться в виде значка размером 128 x 96 пикселей в ImageView.
Чтобы декодер выполнил субдискретизацию изображения и загрузил в память уменьшенную версию, задайте для inSampleSize значение true в объекте BitmapFactory.Options. Например, изображение с разрешением 2048 x 1536, декодированное с параметром inSampleSize, равным 4, дает растровое изображение размером примерно 512 x 384. При загрузке в память будет использовано 0,75 МБ вместо 12 МБ для полного изображения (при условии, что конфигурация растрового изображения – ARGB_8888). Ниже приведен метод расчета размера выборки, который является степенью двойки, на основе целевой ширины и высоты.
Kotlin
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 }
Java
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:
Kotlin
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) } }
Java
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, в котором отображается уменьшенное изображение размером 100 x 100 пикселей, как показано в следующем примере кода:
Kotlin
imageView.setImageBitmap( decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100) )
Java
imageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
Аналогичным образом можно декодировать растровые изображения из других источников, заменяя метод BitmapFactory.decode* на подходящий.