Эффективная загрузка больших растровых изображений

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

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

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

Определение размера и типа растрового изображения

BitmapFactory класс предоставляет несколько методов декодирования(decodeByteArray(), decodeFile(), decodeResource(), и т.д.) для создания Bitmap из различных источников. Выберите наиболее подходящий метод декодирования, основанный на источнике данных изображения. Эти методы пытаются выделить память для построенной растрового изображения и, следовательно, могут легко привести к исключению OutOfMemory . Каждый тип метода декодирования имеет дополнительные сигнатуры, которые позволяют задать настройки декодирования через BitmapFactory.Options класс. Установка inJustDecodeBounds свойства в true при декодировании позволяет избежать выделения памяти, возвращая null для растрового объекта, но устанавливает outWidth, outHeight и 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.75Mb вместо 12 МБ для полного изображения (при условии конфигурации растрового изображения ARGB_8888). Вот метод для вычисления значения коэффициента размера, который является степенью двойки на основе целевой ширины и высоты:

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:

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 пикселей, как показано на следующем примере кода:

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

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