Tworzę aplikację społecznościową, w której obrazy są przesyłane z serwera na urządzenie. Gdy urządzenie ma mniejsze rozdzielczości ekranu, muszę zmienić rozmiar map bitowych na urządzeniu, aby dopasować je do zamierzonych rozmiarów wyświetlania.
Problem polega na tym, że użycie createScaledBitmap powoduje, że napotykam wiele błędów braku pamięci po zmianie rozmiaru hordy miniatur.
Jaki jest najbardziej efektywny pod względem wykorzystania pamięci sposób zmiany rozmiaru map bitowych w systemie Android?
android
performance
bitmap
out-of-memory
Colt McAnlis
źródło
źródło
Odpowiedzi:
Istnieją trzy dominujące sposoby zmiany rozmiaru mapy bitowej w systemie Android, które mają różne właściwości pamięci:
createScaledBitmap API
Ten interfejs API weźmie istniejącą bitmapę i utworzy NOWĄ bitmapę z dokładnie wybranymi wymiarami.
Z drugiej strony możesz uzyskać dokładnie taki rozmiar obrazu, jakiego szukasz (niezależnie od tego, jak wygląda). Jednak wadą jest to, że ten interfejs API wymaga istniejącej mapy bitowej, aby działać . Oznacza to, że obraz musiałby zostać załadowany, zdekodowany i utworzona mapa bitowa, zanim będzie można utworzyć nową, mniejszą wersję. Jest to idealne rozwiązanie, jeśli chodzi o uzyskanie dokładnych wymiarów, ale okropne ze względu na dodatkowe obciążenie pamięci. W związku z tym jest to przełom w przypadku większości twórców aplikacji, którzy mają tendencję do dbania o pamięć
Flaga inSampleSize
BitmapFactory.Options
ma właściwość oznaczoną jako,inSampleSize
która zmieni rozmiar obrazu podczas dekodowania, aby uniknąć potrzeby dekodowania do tymczasowej mapy bitowej. Użyta tutaj wartość całkowita załaduje obraz w rozmiarze zmniejszonym o 1 / x. Na przykład ustawienieinSampleSize
2 zwraca obraz o połowę mniejszy, a ustawienie 4 zwraca obraz o 1/4 rozmiaru. Zasadniczo rozmiary obrazu zawsze będą o potęgę dwa mniejsze niż rozmiar źródła.Z punktu widzenia pamięci używanie
inSampleSize
jest naprawdę szybką operacją. W efekcie dekoduje tylko każdy X piksel obrazu do wynikowej mapy bitowej. Są jednak dwa główne problemyinSampleSize
:Nie podaje dokładnych rozdzielczości . Zmniejsza tylko rozmiar mapy bitowej o potęgę 2.
Nie powoduje zmiany rozmiaru w najlepszej jakości . Większość filtrów zmieniających rozmiar tworzy dobrze wyglądające obrazy, odczytując bloki pikseli, a następnie ważąc je w celu uzyskania danego piksela o zmienionym rozmiarze.
inSampleSize
unika tego wszystkiego, po prostu czytając co kilka pikseli. Rezultat jest dość wydajny i ma mało pamięci, ale cierpi na tym jakość.Jeśli masz do czynienia tylko z pomniejszeniem obrazu o pewien rozmiar pow2, a filtrowanie nie stanowi problemu, nie możesz znaleźć metody bardziej wydajnej pamięci (lub wydajności) niż
inSampleSize
.Flagi inScaled, inDensity, inTargetDensity
Jeśli chcesz przeskalować obraz do wymiaru, który nie jest równy potęgi dwóch, będziesz potrzebować flag
inScaled
,inDensity
i . Gdy flaga została ustawiona, system wyprowadzi wartość skalowania, która ma być zastosowana do twojej mapy bitowej, dzieląc przez wartości.inTargetDensity
BitmapOptions
inScaled
inTargetDensity
inDensity
Użycie tej metody spowoduje zmianę rozmiaru obrazu, a także zastosowanie do niego „filtra zmiany rozmiaru”, to znaczy wynik końcowy będzie wyglądał lepiej, ponieważ podczas zmiany rozmiaru uwzględniono dodatkowe obliczenia. Ale uwaga: ten dodatkowy krok filtra wymaga dodatkowego czasu przetwarzania i może szybko zsumować się w przypadku dużych obrazów, powodując powolne zmiany rozmiaru i dodatkowe alokacje pamięci dla samego filtra.
Generalnie nie jest dobrym pomysłem stosowanie tej techniki do obrazu, który jest znacznie większy niż pożądany rozmiar, z powodu dodatkowego narzutu filtrowania.
Magiczna kombinacja
Z punktu widzenia pamięci i wydajności można połączyć te opcje, aby uzyskać najlepsze wyniki. (ustawienie
inSampleSize
,inScaled
,inDensity
ainTargetDensity
flagi)inSampleSize
zostanie najpierw zastosowany do obrazu, zwiększając go do następnej potęgi dwa WIĘKSZEJ niż docelowy rozmiar. NastępnieinDensity
&inTargetDensity
służą do skalowania wyniku do dokładnych wymiarów, które chcesz, stosując operację filtrowania w celu oczyszczenia obrazu.Połączenie tych dwóch jest znacznie szybszą operacją, ponieważ
inSampleSize
krok ten zmniejszy liczbę pikseli, które wynikowy krok oparty na gęstości będzie musiał zastosować filtr zmiany rozmiaru.Jeśli potrzebujesz dopasować obraz do określonych wymiarów i trochę ładniejszego filtrowania, ta technika jest najlepszym pomostem do uzyskania odpowiedniego rozmiaru, ale jest wykonywana w szybkiej operacji o małej ilości pamięci.
Pobieranie wymiarów obrazu
Uzyskiwanie rozmiaru obrazu bez dekodowania całego obrazu Aby zmienić rozmiar mapy bitowej, musisz znać przychodzące wymiary. Możesz użyć
inJustDecodeBounds
flagi, aby uzyskać wymiary obrazu, bez konieczności dekodowania danych pikseli.Możesz użyć tej flagi, aby najpierw zdekodować rozmiar, a następnie obliczyć odpowiednie wartości skalowania do rozdzielczości docelowej.
źródło
destination width
lub dstWidth w skrócieJakkolwiek ładna (i dokładna) jest ta odpowiedź, jest również bardzo skomplikowana. Zamiast wymyślać koło na nowo, rozważ biblioteki takie jak Glide , Picasso , UIL , Ion lub wiele innych, które wdrażają tę złożoną i podatną na błędy logikę.
Sam Colt zaleca nawet przyjrzenie się Glide i Picasso w filmie Pre-scaling Bitmaps Performance Patterns .
Korzystając z bibliotek, możesz uzyskać każdą wydajność wymienioną w odpowiedzi Colta, ale dzięki znacznie prostszym interfejsom API, które działają konsekwentnie w każdej wersji Androida.
źródło