Wydajne ładowanie dużych map bitowych

Uwaga: istnieje kilka bibliotek, które spełniają wymagania ze sprawdzonymi metodami wczytywania obrazów. Możesz użyć tych bibliotek w swojej aplikacji, aby: najbardziej optymalnie wczytują obrazy. Zalecamy Przesuwanie , która ładuje i wyświetla obrazy tak szybko i płynnie, Inne popularne biblioteki do wczytywania obrazów to Picasso od Square, Coil z Instacart oraz Fryzjer z Facebooka. Te biblioteki upraszczają większość skomplikowanych zadań z mapami bitowymi i innymi typami obrazów na Androidzie.

Obrazy mają różne kształty i rozmiary. W wielu przypadkach są większe niż zwykle interfejsu aplikacji. Na przykład systemowa aplikacja Galeria wyświetla zrobione zdjęcia używając aparatu w urządzeniu z Androidem, który ma zwykle znacznie wyższą rozdzielczość niż ekran. układ pamięci urządzenia.

Biorąc pod uwagę, że masz do czynienia z ograniczoną ilością pamięci, najlepiej przesyłać materiały w niższej rozdzielczości. w pamięci. Wersja w niższej rozdzielczości powinna odpowiadać rozmiarowi komponentu interfejsu, który go wyświetli. Zdjęcie o wyższej rozdzielczości nie przynosi żadnych widocznych korzyści, ale mimo to zużywają cenną pamięć i pochłaniają dodatkowe wymagania związane z wydajnością z powodu

Podczas tej lekcji dowiesz się, jak dekodować duże bitmapy bez konieczności limit pamięci, wczytując w pamięci mniejszą wersję podpróbkowaną.

Odczytywanie wymiarów i typu mapy bitowej

Klasa BitmapFactory udostępnia kilka metod dekodowania (decodeByteArray(), decodeFile(), decodeResource() itd.) do tworzenia obiektu Bitmap z różnych źródeł. Wybierz najbardziej odpowiednią metodę dekodowania na podstawie źródła danych obrazu. Te metody mają na celu przydzielanie pamięci dla utworzonej mapy bitowej, co może łatwo spowodować OutOfMemory wyjątek. Każdy typ metody dekodowania ma dodatkowe podpisy, które umożliwiają określenie dekodowania za pomocą klasy BitmapFactory.Options. Ustawianie właściwości inJustDecodeBounds na true podczas dekodowania pozwala uniknąć alokacji pamięci, zwracając null dla obiektu bitmapy, ale ustawiając outWidth, outHeight i outMimeType. Ta metoda pozwala odczytać wymiarów i typu danych zdjęć przed zbudowaniem (i przydziału pamięci) bitmapy.

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;

Aby uniknąć java.lang.OutOfMemory wyjątków, sprawdź wymiary mapy bitowej przed go dekodować, chyba że ufasz źródłu i dostarczysz danych obrazu o przewidywalnych rozmiarach który wygodnie mieści się w dostępnej pamięci.

Wczytywanie do pamięci wersji skalowanej w dół

Znane są już wymiary obrazu, można więc na ich podstawie zdecydować, czy jego rozmiar wczytywane do pamięci lub powinna zostać wczytana wersja podpróbkowana. Czynniki, które warto wziąć pod uwagę, rozważ:

  • Szacowane wykorzystanie pamięci przez wczytanie całego obrazu w pamięci.
  • Ilość pamięci, jaką chcesz przeznaczyć na wczytanie tego obrazu na podstawie dowolnej innej pamięci wymagania Twojego zgłoszenia.
  • Wymiary docelowego elementu ImageView lub komponentu interfejsu, którego dotyczy obraz ma zostać załadowany.
  • Rozmiar i gęstość ekranu na bieżącym urządzeniu.

Na przykład nie warto ładować obrazu o rozdzielczości 1024 x 768 pikseli do pamięci, jeśli w przyszłości wyświetlana w miniaturze o wymiarach 128 x 96 pikseli w ImageView.

Aby nakazać dekoderowi pobieranie podpróbkowanego obrazu przez wczytanie mniejszej wersji obrazu do pamięci, ustaw inSampleSize na true w obiekcie BitmapFactory.Options. Na przykład obraz o rozdzielczości 2048 x 1536, który jest dekodowane z ustawieniem inSampleSize o wartości 4, co daje bitmapa o wymiarach około 512 x 384 piksele. Załadowanie tego pliku do pamięci zajmuje 0,75 MB, a nie 12 MB, image (przy założeniu, że konfiguracja bitmapy to ARGB_8888). Oto metody obliczania wartości rozmiaru próbki, która jest potęgą równa 2, na podstawie szerokości docelowej oraz wysokość:

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;
}

Uwaga: oblicza się potęgę równa 2 wartości, ponieważ dekoder używa wartość końcową, zaokrąglając w dół do najbliższej potęgi liczby dwóch, zgodnie z dokumentacją funkcji inSampleSize.

Aby użyć tej metody, najpierw zdekoduj z wartością inJustDecodeBounds ustawioną na true, a potem przekaż opcje przez a następnie ponownie zdekodować z użyciem nowej wartości inSampleSize i inJustDecodeBounds ustawionej na 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);
}

Ta metoda ułatwia wczytanie bitmapy o dowolnie dużej wielkości do elementu ImageView z miniaturą o wymiarach 100 × 100 pikseli. Jak pokazano w tym przykładzie. kod:

Kotlin

imageView.setImageBitmap(
        decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100)
)

Java

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

W podobny sposób możesz dekodować mapy bitowe z innych źródeł, zastępując odpowiednią metodę BitmapFactory.decode*.