Uwaga: jest kilka bibliotek, które korzystają ze sprawdzonych metod wczytywania obrazów. Możesz używać tych bibliotek w swojej aplikacji, aby wczytywać obrazy w najbardziej zoptymalizowany sposób. Zalecamy korzystanie z biblioteki Glide, która ładuje oraz wyświetla obrazy tak szybko i płynnie, jak to tylko możliwe. Inne popularne biblioteki wczytywania obrazów to Picasso od Square, Coil od Instacart i Fresco z Facebooka. Biblioteki te upraszczają większość złożonych zadań związanych z mapami bitowymi i innymi typami obrazów na Androidzie.
Obrazy mają różne kształty i rozmiary. W wielu przypadkach są one większe niż wymagane w przypadku typowego interfejsu aplikacji. Na przykład systemowa aplikacja Galeria wyświetla zdjęcia zrobione aparatem urządzenia z Androidem, które mają zwykle znacznie większą rozdzielczość niż gęstość ekranu urządzenia.
Ponieważ pracujesz z ograniczoną pamięcią, najlepiej by było wczytywać w pamięci tylko wersję o niższej rozdzielczości. Wersja w niższej rozdzielczości powinna pasować do rozmiaru komponentu UI, który ją wyświetla. Obraz o wyższej rozdzielczości nie zapewnia żadnych widocznych korzyści, ale nadal zajmuje cenną pamięć i generuje dodatkową wydajność ze względu na dodatkowe skalowanie w locie.
W tej lekcji zaprezentujemy, jak dekodować duże mapy bitowe bez przekraczania limitu pamięci poszczególnych aplikacji przez wczytywanie do pamięci mniejszej wersji podpróbkowanej.
Odczytywanie wymiarów i typu mapy bitowej
Klasa BitmapFactory
udostępnia kilka metod dekodowania (decodeByteArray()
, decodeFile()
, decodeResource()
itd.) służących do tworzenia elementów Bitmap
z różnych źródeł. Na podstawie źródła danych obrazu wybierz najbardziej odpowiednią metodę dekodowania. Te metody próbują przydzielać pamięć na potrzeby skonstruowanej bitmapy, dlatego mogą łatwo skutkować wyjątkiem OutOfMemory
. Każdy typ metody dekodowania ma dodatkowe podpisy, które pozwalają określić opcje dekodowania za pomocą klasy BitmapFactory.Options
. Ustawienie właściwości inJustDecodeBounds
na true
podczas dekodowania unika alokacji pamięci, ponieważ zwraca wartość null
dla obiektu bitmapy, ale ustawia się outWidth
, outHeight
i outMimeType
. Ta metoda pozwala odczytać wymiary i typ danych obrazu przed utworzeniem (i przydziałem pamięci) mapy bitowej.
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ąć wyjątków java.lang.OutOfMemory
, sprawdź wymiary bitmapy przed jej zdekodowaniem, chyba że masz absolutnie pewność, że źródło udostępnia dane obrazu o przewidywalnych rozmiarach, które wygodnie mieszczą się w dostępnej pamięci.
Wczytywanie do pamięci wersji przeskalowanej w dół
Znane już wymiary obrazu pozwalają zdecydować, czy w pamięci ma zostać wczytany cały obraz, czy też powinna zostać wczytana jego wersja podpróbkowana. Weź pod uwagę te kwestie:
- Szacunkowe wykorzystanie pamięci podczas wczytywania całego obrazu w pamięci.
- Ilość pamięci, jaką chcesz załadować do wczytywania tego obrazu, biorąc pod uwagę inne wymagania dotyczące pamięci aplikacji.
- Wymiary docelowego elementu
ImageView
lub komponentu UI, do którego ma być wczytywany obraz. - Rozmiar ekranu i gęstość obecnego urządzenia.
Na przykład nie warto wczytywać do pamięci obrazu o rozdzielczości 1024 × 768 pikseli, jeśli w zasadzie ImageView
zostanie on wyświetlony jako miniatura o wymiarach 128 × 96 pikseli.
Aby nakazać dekoderowi podpróbkowanie obrazu przez wczytanie mniejszej wersji w pamięci, ustaw inSampleSize
na true
w obiekcie BitmapFactory.Options
. Na przykład obraz o rozdzielczości 2048 x 1536 po zdekodowaniu z użyciem parametru inSampleSize
o wartości 4 powoduje utworzenie mapy bitowej o wymiarach około 512 x 384. Wczytanie tego do pamięci zajmuje 0,75 MB, a nie 12 MB dla pełnego obrazu (przy założeniu, że bitmapa ma konfigurację ARGB_8888
). Oto sposób obliczania wartości próbki, która jest potęgą 2 na podstawie docelowej szerokości i wysokości:
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: potęga równa 2 jest obliczana, ponieważ dekoder wykorzystuje wartość końcową, zaokrąglając w dół do najbliższej potęgi dwa, zgodnie z dokumentacją inSampleSize
.
Aby użyć tej metody, najpierw dekoduj z parametrem inJustDecodeBounds
ustawionym na true
, przekaż opcje, a następnie ponownie zdekoduj, używając 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 dowolnym rozmiarze do elementu ImageView
z miniaturą o wymiarach 100 × 100 pikseli, jak w tym przykładzie:
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 w razie potrzeby odpowiednią metodę BitmapFactory.decode*
.