Optymalizacja obrazów bitmapowych

Jeśli nie zachowasz ostrożności, praca z obrazami może szybko spowodować problemy z wydajnością. Nawet niewielka grafika w skompresowanym formacie, takim jak JPG lub PNG, po zdekodowaniu do wyświetlenia może stać się dużą bitmapą. Jeśli nie będziesz efektywnie korzystać z grafiki, możesz napotkać problemy z pamięcią, które mogą negatywnie wpłynąć na działanie Twojej aplikacji i innych aplikacji na urządzeniu. Aby zapewnić jak najlepsze działanie aplikacji, postępuj zgodnie z tymi sprawdzonymi metodami.

Używanie bibliotek do wczytywania obrazów

Możesz zwiększyć wydajność aplikacji, używając bibliotek do wczytywania obrazów, takich jak Coil (w przypadku projektów Kotlin-first) lub Glide (w przypadku projektów Java). Te biblioteki zmniejszają wykorzystanie pamięci przez aplikację, wykonując takie czynności jak buforowanie obrazów, próbkowanie grafiki w dół w razie potrzeby i recykling obiektów graficznych.

Zmniejsz próbkowanie obrazów

Upewnij się, że używasz rozmiaru obrazu odpowiedniego do Twoich potrzeb. Unikaj wczytywania dużego obrazu o wysokiej rozdzielczości do małego kontenera (np. miniatury). Zamiast tego użyj próbkowania w dół, aby zmniejszyć obraz przed jego zdekodowaniem do pamięci.

Zmniejszanie próbkowania po stronie klienta

Biblioteki do wczytywania obrazów, takie jak Coil i Glide, automatycznie obsługują próbkowanie w dół. Strategie downsamplingu możesz skonfigurować za pomocą ImageLoader (w przypadku Coil) lub DownsampleStrategy (w przypadku Glide). Jeśli zarządzasz bitmapami ręcznie, możesz użyć inSampleSize, aby zdekodować mniejszą wersję. Aby zrobić to bezpiecznie, najpierw ustaw inJustDecodeBounds na true, aby odczytać wymiary obrazu bez przydzielania pamięci, oblicz rozmiar próbki, ustaw inSampleSize na tę wartość, ustaw inJustDecodeBounds na false, a następnie zdekoduj obraz.

Preferuj zmianę rozmiaru po stronie serwera

Jeśli to możliwe, poproś o dokładne wymiary obrazu, których potrzebujesz, bezpośrednio z serwera backendu. Zmniejsza to wykorzystanie sieci i pamięci podręcznej na dysku, a także wykorzystanie pamięci, ponieważ nie trzeba jej obciążać narzutem zmiany rozmiaru obrazów na urządzeniu.

Możesz skonfigurować biblioteki tak, aby dynamicznie dołączały docelowy rozmiar widoku do adresu URL obrazu. Na przykład Coil umożliwia to za pomocą niestandardowych przechwytywaczy, a Glide obsługuje to za pomocą niestandardowych modułów ładowania modeli (takich jak BaseGlideUrlLoader).

Unikanie nieograniczonych rozmiarów układu

Aby moduły wczytywania obrazów mogły skutecznie zmniejszać próbkowanie (po stronie klienta lub serwera), muszą znać rozmiar docelowy przed wykonaniem żądania.

Unikaj używania wrapContentSize i nie pozostawiaj wymiarów bez ograniczeń w przypadku elementów kompozycyjnych, które wczytują obrazy zdalne. Jeśli te biblioteki nie mogą określić granic docelowych, wczytują oryginalny obraz w pełnym rozmiarze. Może to prowadzić do wczytywania obrazu o znacznie większym rozmiarze niż jest to konieczne, co zwiększa wykorzystanie pamięci i czas oczekiwania.

Zamiast tego ustaw wyraźne wymiary w komponentach obrazu (np. za pomocą Modifier.size) lub zdefiniuj format obrazu. Dzięki temu mechanizm układu może z góry obliczyć dokładny rozmiar docelowy w pikselach, który moduł wczytywania obrazów może następnie wykorzystać do żądania i dekodowania zasobu o odpowiednim rozmiarze.

Dostarczanie zasobów alternatywnych dla różnych rozmiarów ekranu

Jeśli dołączasz do aplikacji obrazy, rozważ dostarczenie zasobów o różnych rozmiarach dla różnych rozdzielczości urządzeń. Może to pomóc zmniejszyć rozmiar pobieranej aplikacji na urządzeniach i poprawić wydajność, ponieważ na urządzeniu o niższej rozdzielczości będzie ładowany obraz o niższej rozdzielczości. Więcej informacji o udostępnianiu alternatywnych bitmap dla różnych rozmiarów urządzeń znajdziesz w dokumentacji dotyczącej alternatywnych bitmap.

Nie stosuj dopełnienia bezpośrednio

Czasami może być konieczne dodanie do obrazu dopełnienia. Możesz na przykład otoczyć obraz przezroczystym obramowaniem, aby uzyskać efekt letterbox. W takich sytuacjach nie dodawaj dopełnienia bezpośrednio do obrazu, zmieniając jego wymiary. Zamiast tego pozostaw wymiary obrazu bez zmian i dostosuj jego położenie na ekranie za pomocą InsetDrawable. Możesz też dodać dopełnienie do elementu Composable lub widoku zawierającego obraz.

Wybór odpowiedniego formatu pikseli

Znajdź złoty środek między pamięcią a jakością, wybierając odpowiedni format pikseli. Używaj formatu RGB_565, gdy nie potrzebujesz przezroczystości. Ten format zajmuje o połowę mniej pamięci niż domyślny format ARGB_8888.

W Glide możesz to skonfigurować za pomocą DecodeFormat. W przypadku Coil możesz użyć właściwości bitmapConfig.

W miarę możliwości używaj wektorów

W przypadku obrazów składających się z kształtów geometrycznych grafika wektorowa jest znacznie mniejsza niż bitmapa i płynnie skaluje się do dowolnej gęstości wyświetlania. W odpowiednich przypadkach używaj elementów takich jak ShapeDrawable do reprezentowania grafiki.

Zwalniaj i ponownie używaj bitmap, gdy tylko jest to możliwe

Duże pliki graficzne mogą zajmować dużo pamięci. Aby zmniejszyć ich wpływ, należy w miarę możliwości zwalniać lub ponownie wykorzystywać obiekty graficzne.

Jeśli używasz biblioteki do wczytywania obrazów, pamiętaj, aby zwolnić bitmapy do zarządzanej puli biblioteki, gdy nie będą już potrzebne. Biblioteka może w razie potrzeby ponownie wykorzystać obiekty i zachować bufor pamięci na przyszłość.

Jeśli zarządzasz grafiką ręcznie, po zakończeniu pracy z bitmapami zwolnij je, wywołując metodę Bitmap.recycle i natychmiast odrzucając odwołanie Bitmap, zamiast polegać na czyszczeniu pamięci.

Inne porady i wskazówki

W tej sekcji znajdziesz kilka innych sposobów na poprawę wydajności aplikacji podczas obsługi grafiki.

Nie dołączaj dużych obrazów do pliku AAB/APK

Jedną z głównych przyczyn dużego rozmiaru pobieranej aplikacji jest grafika spakowana w pliku AAB lub APK. Użyj narzędzia APK Analyzer, aby upewnić się, że nie pakujesz plików graficznych większych niż wymagane. Zmniejsz rozmiary lub rozważ umieszczenie obrazów na serwerze i pobieranie ich tylko wtedy, gdy są potrzebne.

Znajdowanie zbędnych bitmap

Jeśli masz kilka kopii tego samego obrazu, marnujesz pamięć. Za pomocą programu profilującego Androida Studio możesz wykryć zbędne elementy graficzne. Użyj analizatora zrzutu stosu, aby przechwycić zrzut stosu, i przefiltruj wyniki, wybierając ustawienie Zduplikowane bitmapy.

Jeśli używasz ImageBitmap, przed rysowaniem wywołaj prepareToDraw

Jeśli używasz ImageBitmap, aby rozpocząć proces przesyłania tekstury do GPU, wywołaj ImageBitmap#prepareToDraw() przed jej narysowaniem. Pomaga to GPU przygotować teksturę i poprawić wydajność wyświetlania obrazu na ekranie. Większość bibliotek wczytywania obrazów już wykonuje tę optymalizację, ale jeśli pracujesz z klasą ImageBitmap, warto o tym pamiętać.

Zamiast Painter lepiej przekazywać do funkcji kompozycyjnej Int DrawableRes lub adres URL jako parametry.

Ze względu na złożoność obsługi obrazów (np. napisanie funkcji równości dla Bitmaps byłoby kosztowne pod względem obliczeniowym) interfejs API Painter nie jest wyraźnie oznaczony jako stabilny za pomocą adnotacji @Stable. Niestabilne klasy mogą prowadzić do niepotrzebnych rekompozycji, ponieważ kompilator nie może łatwo wywnioskować, czy dane uległy zmianie.

Dlatego zalecamy przekazywanie do funkcji kompozycyjnej adresu URL lub identyfikatora obiektu rysowalnego jako parametrów zamiast przekazywania Painter jako parametru.

// Prefer this:
@Composable
fun MyImage(url: String) {

}
// Over this:
@Composable
fun MyImage(painter: Painter) {

}