Optymalizacja obrazów bitmapowych

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

Korzystanie z bibliotek wczytywania obrazów

Możesz zwiększyć wydajność aplikacji, korzystając z bibliotek do wczytywania obrazów, takich jak Coil (w przypadku projektów opartych na Kotlinie) lub Glide (w przypadku projektów opartych na Javie). Biblioteki te zmniejszają wykorzystanie pamięci przez aplikację, m.in. dzięki buforowaniu obrazów, próbkowaniu w dół grafiki w razie potrzeby i ponownemu wykorzystywaniu obiektów graficznych.

Zmniejszanie próbkowania obrazów

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

Downsampling po stronie klienta

Biblioteki do wczytywania obrazów, takie jak Coil i Glide, automatycznie obsługują próbkowanie w dół. Strategie próbkowania w dół 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 to zrobić 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.

Preferowanie zmiany rozmiaru po stronie serwera

W miarę możliwości przesyłaj żądania dotyczące dokładnych wymiarów obrazu bezpośrednio z serwera backendu. Zmniejsza to wykorzystanie sieci i rozmiar pamięci podręcznej dysku, a jednocześnie zmniejsza wykorzystanie pamięci, ponieważ nie trzeba zmieniać rozmiaru obrazów na urządzeniu.

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

Unikaj nieograniczonych rozmiarów układu

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

Unikaj używania wrapContentSize lub pozostawiania wymiarów bez ograniczeń w komponentach kompozycyjnych, które wczytują obrazy zdalne. Jeśli te biblioteki nie mogą określić docelowych granic, wracają do wczytywania oryginalnego obrazu w pełnym rozmiarze. Może to prowadzić do wczytywania znacznie większego obrazu niż jest to konieczne, co zwiększa wykorzystanie pamięci i wydłuża czas oczekiwania.

Zamiast tego ustaw jawne wymiary w kompozycji obrazu (np. za pomocą Modifier.size) lub zdefiniuj format obrazu. Dzięki temu silnik układu może z wyprzedzeniem obliczyć dokładny docelowy rozmiar w pikselach, który moduł wczytywania obrazów może następnie wykorzystać do wysłania żądania i zdekodowania zasobu o odpowiednim rozmiarze.

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

Jeśli wysyłasz obrazy z aplikacją, rozważ dostarczenie zasobów o różnych rozmiarach dla różnych rozdzielczości urządzeń. Może to pomóc zmniejszyć rozmiar pliku do pobrania aplikacji na urządzeniach i zwiększyć wydajność, ponieważ na urządzeniach o niższej rozdzielczości będzie wczytywany obraz o niższej rozdzielczości. Więcej informacji o dostarczaniu alternatywnych bitmap dla różnych rozmiarów urządzeń znajdziesz w dokumentacji dotyczącej alternatywnych bitmap.

Nie stosuj bezpośrednio dopełnienia

Czasami może być konieczne dodanie do obrazu dopełnienia. Możesz na przykład chcieć, aby obraz był otoczony przez przezroczystą ramkę, co pozwoli uzyskać efekt letterboxingu. W takich sytuacjach nie dodawaj do obrazu dopełnienia, które zmienia jego wymiary. Zamiast tego pozostaw wymiary obrazu bez zmian i dostosuj jego położenie na ekranie za pomocą InsetDrawable. Możesz też dodać do elementu Composable lub widoku zawierającego obraz dopełnienie.

Wybór odpowiedniego formatu piksela

Wybierz odpowiedni format pikseli, aby znaleźć złoty środek między pamięcią a jakością. Użyj 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 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 można ją płynnie skalować do dowolnej gęstości wyświetlania. W odpowiednich przypadkach używaj elementów takich jak ShapeDrawable do przedstawiania grafiki.

Zwalniaj i ponownie wykorzystuj mapy bitowe, gdy jest to możliwe.

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

Jeśli używasz biblioteki wczytywania obrazów, pamiętaj, aby zwalniać mapy bitowe do zarządzanej puli biblioteki, gdy nie są już potrzebne. Biblioteka może w razie potrzeby ponownie użyć obiektów i utrzymuje bufor pamięci dostępny na przyszłość.

Jeśli zarządzasz grafiką ręcznie, po zakończeniu pracy z mapami bitowymi zwolnij je, wywołując funkcję Bitmap.recycle, i natychmiast odrzuć odwołanie Bitmap, zamiast polegać na odśmiecaniu 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 pakuj dużych obrazów w pliku AAB/APK.

Jedną z głównych przyczyn dużego rozmiaru pobieranej aplikacji są grafiki spakowane w pliku AAB lub APK. Użyj analizatora APK, aby sprawdzić, czy nie pakujesz większych niż wymagane plików obrazów. Zmniejsz rozmiary lub rozważ umieszczenie obrazów na serwerze i pobieranie ich tylko w razie potrzeby.

Znajdowanie zbędnych bitmap

Jeśli masz kilka kopii tego samego obrazu, marnujesz pamięć. Za pomocą profilera Androida Studio możesz wykrywać zbędne grafiki. Użyj analizatora zrzutu stosu, aby przechwycić zrzut stosu, i filtruj wyniki, wybierając ustawienie zduplikowanych bitmap.

Jeśli używasz funkcji ImageBitmap, przed rysowaniem wywołaj funkcję prepareToDraw.

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

Zamiast Painter, w miarę możliwości przekazuj 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 obliczeniowo) interfejs Painter API nie jest wyraźnie oznaczony jako stabilny za pomocą adnotacji @Stable. Niestabilne klasy mogą prowadzić do niepotrzebnych ponownych kompozycji, ponieważ kompilator nie może łatwo wywnioskować, czy dane uległy zmianie.

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

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

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

}