Optymalizacja wykorzystania pamięci

Optymalizacja pamięci jest kluczowa dla zapewnienia płynnego działania, zapobiegania awariom aplikacji oraz utrzymania stabilności systemu i sprawnego działania platformy. Chociaż zużycie pamięci należy monitorować i optymalizować w każdej aplikacji, aplikacje z treściami na urządzenia telewizyjne mają specyficzne problemy, które różnią się od typowych aplikacji na urządzenia przenośne z Androidem.

Wysokie zużycie pamięci może powodować problemy z działaniem aplikacji i systemu, w tym:

  • Sama aplikacja może działać wolno lub z opóźnieniem, a w najgorszym przypadku może zostać zamknięta.
  • Usługi systemowe widoczne dla użytkownika (sterowanie głośnością, panel ustawień obrazu, asystent głosowy itp.) działają z dużym opóźnieniem lub wcale.
  • Proces demona low memory killer (LMK) może reagować na wysokie obciążenie pamięci, zamykając najmniej istotne procesy. Następnie te komponenty mogą wkrótce zostać ponownie uruchomione, co spowoduje dalsze skoki w konkurencji o zasoby, które mogą bezpośrednio wpływać na aplikację działającą na pierwszym planie.
  • Przejście do Launchera może być znacznie opóźnione, a aplikacja na pierwszym planie może wydawać się nieodpowiadająca, dopóki przejście się nie zakończy.
  • System może zacząć używać bezpośredniego odzyskiwania, tymczasowo wstrzymując wykonywanie wątków podczas oczekiwania na alokację pamięci. Może się to zdarzyć w przypadku dowolnego wątku, np. głównego wątku lub wątków związanych z kodekiem, co może powodować utratę klatek audio i wideo oraz błędy interfejsu.

Kwestie związane z pamięcią na urządzeniach telewizyjnych

Urządzenia telewizyjne mają zwykle znacznie mniej pamięci niż telefony czy tablety. Na przykład konfiguracja, którą możemy zobaczyć na telewizorze, to 1 GB pamięci RAM i rozdzielczość wideo 1080p. Jednocześnie większość aplikacji na telewizory ma podobne funkcje, a co za tym idzie, podobne wdrożenia i częste problemy. Te 2 sytuacje stwarzają problemy, które nie występują w przypadku innych typów urządzeń i aplikacji:

  • Aplikacje multimedialne na telewizory zwykle składają się z widoków obrazów w siatceobrazów tła na pełnym ekranie, co wymaga wczytywania do pamięci wielu obrazów w krótkim czasie.
  • Aplikacje telewizyjne odtwarzają strumienie multimedialne, które wymagają przydzielenia określonej ilości pamięci do odtwarzania wideo i audio oraz znacznych buforów multimedialnych, aby zapewnić płynne odtwarzanie.
  • Dodatkowe funkcje multimedialne (przewijanie, zmiana odcinka, zmiana ścieżki audio itp.) mogą powodować dodatkowe obciążenie pamięci, jeśli nie zostaną prawidłowo wdrożone.

Informacje o urządzeniach TV

Ten przewodnik dotyczy głównie wykorzystania pamięci przez aplikacje i docelowych wartości pamięci na urządzeniach z małą ilością pamięci RAM.

W przypadku urządzeń telewizyjnych weź pod uwagę te cechy:

  • Pamięć urządzenia: ilość pamięci RAM zainstalowanej na urządzeniu.
  • Rozdzielczość interfejsu urządzenia: rozdzielczość, w której urządzenie renderuje interfejs systemu operacyjnego i aplikacji. Zwykle jest ona niższa niż rozdzielczość wideo urządzenia.
  • Rozdzielczość wideo: maksymalna rozdzielczość, w której urządzenie może odtwarzać filmy.

Prowadzi to do kategoryzacji różnych typów urządzeń i określenia, jak powinny one korzystać z pamięci.

Podsumowanie urządzeń TV

Pamięć urządzenia Rozdzielczość wideo na urządzeniu Rozdzielczość interfejsu urządzenia isLowRAMDevice()
1 GB 1080p 720p Tak
1,5 GB 2160p 1080p Tak
≥ 1,5 GB 1080p 720p lub 1080p Nie*
≥2 GB 2160p 1080p Nie*

Urządzenia telewizyjne z niewielką ilością pamięci RAM

Te urządzenia mają ograniczoną ilość pamięci i będą zgłaszać wartość ActivityManager.isLowRAMDevice() jako „prawda”. Aplikacje działające na telewizorach z małą ilością pamięci RAM muszą wdrażać dodatkowe środki kontroli pamięci.

Do tej kategorii zaliczamy urządzenia o tych cechach:

  • Urządzenia z 1 GB pamięci RAM: 1 GB pamięci RAM, rozdzielczość interfejsu 720p/HD (1280 x 720), rozdzielczość wideo 1080p/FullHD (1920 x 1080).
  • Urządzenia z 1,5 GB pamięci: 1,5 GB pamięci RAM, rozdzielczość interfejsu 1080p/FullHD (1920x1080), rozdzielczość wideo 2160p/UltraHD/4K (3840x2160)
  • Inne sytuacje, w których producent OEM zdefiniował flagę ActivityManager.isLowRAMDevice() z powodu dodatkowych ograniczeń pamięci.

Zwykłe telewizory

Na tych urządzeniach nie występuje tak duże obciążenie pamięci. Uważamy, że urządzenia te mają następujące cechy:

  • ≥ 1,5 GB pamięci RAM, interfejs użytkownika w rozdzielczości 720p lub 1080p oraz rozdzielczość wideo 1080p.
  • co najmniej 2 GB pamięci RAM, interfejs użytkownika 1080p i rozdzielczość wideo 1080p lub 2160p,

Nie oznacza to, że aplikacje nie powinny dbać o wykorzystanie pamięci na tych urządzeniach, ponieważ niektóre przypadki niewłaściwego wykorzystania pamięci mogą nadal wyczerpywać dostępną pamięć i działać nieprawidłowo.

Docelowe wartości pamięci na urządzeniach TV z małą ilością pamięci RAM

Podczas pomiaru pamięci na tych urządzeniach zdecydowanie zalecamy monitorowanie każdego obszaru pamięci za pomocą profilera pamięci w Android Studio. Aplikacje na telewizory powinny profilować wykorzystanie pamięci i dążyć do tego, aby ich kategorie mieściły się poniżej progów określonych w tej sekcji.

profiler pamięci,

W sekcji Jak jest liczona pamięć znajdziesz szczegółowe wyjaśnienie podanych wartości pamięci. W przypadku definicji progów dla aplikacji na telewizory skupimy się na 3 kategoriach pamięci:

  • Anonimowe + zamiana: pamięć składająca się z pamięci Java + natywnej + przydzielonej do stosu w Android Studio.
  • Grafika: raportowana bezpośrednio w narzędziu do profilowania. Zwykle składa się z tekstur graficznych.
  • Plik: w Android Studio zgłaszany jako kategorie „Kod” i „Inne”.

Zgodnie z tymi definicjami w tabeli poniżej podano maksymalną wartość, której powinna używać każda grupa pamięci:

Typ pamięci Purpose Limity wykorzystania (1 GB)
Anonimowe + zamiana (Java + natywny + stos) Używana do alokacji, buforów multimediów, zmiennych i innych zadań wymagających dużej ilości pamięci. < 160 MB
Grafika Używane przez procesor graficzny do tekstur i buforów związanych z wyświetlaniem. 30–40 MB
Plik Używana w przypadku stron z kodem i plików w pamięci. 60–80 MB

Maksymalna łączna ilość pamięci (Anon+Swap + Graphics + File) nie może przekraczać:

  • 280 MB całkowitego wykorzystania pamięci (Anon+Swap + Graphics + File) w przypadku urządzeń z 1 GB pamięci RAM.

Zdecydowanie zalecamy, aby nie przekraczać tych wartości:

  • 200 MB wykorzystania pamięci w przypadku (Anon+Swap + Graphics).

Pamięć pliku

Ogólne wskazówki dotyczące pamięci opartej na plikach:

  • Ogólnie rzecz biorąc, pamięć plików jest dobrze obsługiwana przez zarządzanie pamięcią systemu operacyjnego.
  • Obecnie nie uważamy, że jest to główna przyczyna obciążenia pamięci.

Jednak w przypadku pamięci plików:

  • Nie uwzględniaj w kompilacji nieużywanych bibliotek i w miarę możliwości używaj małych podzbiorów bibliotek zamiast pełnych.
  • Nie otwieraj dużych plików w pamięci i zwalniaj je, gdy tylko skończysz z nimi pracę.
  • Zminimalizuj rozmiar skompilowanego kodu w przypadku klas Java i Kotlin. Zapoznaj się z przewodnikiem Zmniejszanie, zaciemnianie i optymalizowanie aplikacji.

Konkretne rekomendacje dotyczące telewizorów

W tej sekcji znajdziesz konkretne rekomendacje dotyczące optymalizacji wykorzystania pamięci na urządzeniach TV.

Pamięć karty graficznej

Używaj odpowiednich formatów i rozdzielczości obrazów.

  • Nie wczytuj obrazów o rozdzielczości wyższej niż rozdzielczość interfejsu urządzenia. Na przykład obrazy 1080p powinny być zmniejszane do 720p na urządzeniu z interfejsem 720p.
  • W miarę możliwości używaj bitmap obsługiwanych sprzętowo.
    • W bibliotekach takich jak Glide włącz funkcję Downsampler.ALLOW_HARDWARE_CONFIG, która jest domyślnie wyłączona. Włączenie tej opcji zapobiega duplikowaniu bitmap, które w przeciwnym razie znajdowałyby się zarówno w pamięci karty graficznej, jak i w pamięci anonimowej.
  • Unikaj renderowania pośredniego i ponownego renderowania.
    • Możesz je zidentyfikować za pomocą Android GPU Inspector:
    • W sekcji „Tekstury” poszukaj obrazów, które są etapami tworzenia ostatecznej wersji, a nie tylko elementami ją tworzącymi. Zwykle jest to tzw. „renderowanie pośrednie”.
    • W przypadku aplikacji na Androida korzystających z pakietu Android SDK możesz często usunąć te elementy, używając flagi układu forceHasOverlappedRendering:false do wyłączenia renderowania pośredniego dla tego układu.
    • Więcej informacji znajdziesz w artykule Unikanie nakładających się renderowań.
  • W miarę możliwości unikaj wczytywania obrazów zastępczych. Używaj @android:color/ lub @color w przypadku tekstur zastępczych.
  • Unikaj łączenia wielu obrazów na urządzeniu, jeśli można to zrobić w trybie offline. Wolisz wczytywać samodzielne obrazy zamiast komponować obrazy z pobranych obrazów.
  • Aby lepiej radzić sobie z mapami bitowymi, postępuj zgodnie z tym przewodnikiem.

Anon+Swap memory

Anon+Swap to alokacje Native + Java + Stack w profilerze pamięci Android Studio. Użyj ActivityManager.isLowMemoryDevice() aby sprawdzić, czy urządzenie ma ograniczoną ilość pamięci, i dostosować się do tej sytuacji zgodnie z tymi wytycznymi.

  • Media:
    • Określ zmienny rozmiar buforów multimediów w zależności od pamięci RAM urządzenia i rozdzielczości odtwarzania wideo. Powinno to odpowiadać 1 minucie odtwarzania filmu:
      1. 40–60 MB na 1 GB / 1080p
      2. 60–80 MB w przypadku 1,5 GB / 1080p
      3. 80–100 MB w przypadku 1,5 GB / 2160p
      4. 100–120 MB w przypadku 2 GB / 2160p
    • Zwalnianie pamięci multimediów podczas zmiany odcinka, aby zapobiegać zwiększaniu się łącznej ilości pamięci anonimowej.
    • Zwalniaj i natychmiast zatrzymuj zasoby multimedialne, gdy aplikacja zostanie zatrzymana: używaj wywołań zwrotnych cyklu życia aktywności do obsługi zasobów audio i wideo. Jeśli nie jesteś aplikacją audio, zatrzymaj odtwarzanie, gdy w Twoich aktywnościach nastąpi onStop(), zapisz wszystkie wykonywane czynności i ustaw zwolnienie zasobów. Aby zaplanować pracę, która może być potrzebna później. Zapoznaj się z sekcją Zadania i alarmy.
    • Zwróć uwagę na pamięć bufora podczas przewijania filmu: deweloperzy często przydzielają dodatkowe 15–60 sekund przyszłych treści podczas przewijania, aby przygotować film dla użytkownika, ale powoduje to dodatkowe obciążenie pamięci. Zwykle nie należy buforować więcej niż 5 sekund przyszłego bufora, dopóki użytkownik nie wybierze nowej pozycji w filmie. Jeśli podczas przewijania konieczne jest buforowanie z wyprzedzeniem dodatkowego czasu, wykonaj te czynności:
      • Wcześniejsze przydzielanie bufora wyszukiwania i ponowne jego używanie.
      • Rozmiar bufora nie powinien przekraczać 15–25 MB (w zależności od pamięci urządzenia).
  • Przydziały:
    • Skorzystaj z wskazówek dotyczących pamięci graficznej, aby mieć pewność, że nie powielasz obrazów w pamięci anonimowej.
      • Obrazy często zajmują najwięcej pamięci, więc ich duplikowanie może mocno obciążać urządzenie. Jest to szczególnie ważne podczas intensywnego przeglądania widoków siatki obrazów.
    • Zwalniaj przydziały, usuwając odwołania podczas przenoszenia ekranów: upewnij się, że nie pozostały żadne odwołania do bitmap i obiektów.
  • Biblioteki:
    • Przydzielanie pamięci do profili z bibliotek podczas dodawania nowych, ponieważ mogą one też wczytywać dodatkowe biblioteki, które również mogą przydzielać pamięć i tworzyć powiązania.
  • Nawiązywanie kontaktów:
    • Nie wykonuj blokujących wywołań sieciowych podczas uruchamiania aplikacji, ponieważ spowalniają one uruchamianie aplikacji i powodują dodatkowe obciążenie pamięci podczas uruchamiania, gdy pamięć jest szczególnie ograniczona przez wczytywanie aplikacji. Najpierw wyświetlaj ekran ładowania lub ekran powitalny, a żądania sieciowe wysyłaj dopiero po wyświetleniu interfejsu.

Powiązania

Powiązania zwiększają obciążenie pamięci, ponieważ przenoszą do pamięci inne aplikacje lub zwiększają zużycie pamięci przez powiązaną aplikację (jeśli jest już w pamięci), aby ułatwić wywołanie interfejsu API. W rezultacie zmniejsza to dostępną pamięć dla aplikacji działającej na pierwszym planie. Podczas wiązania usługi pamiętaj, kiedy i jak długo używasz tego wiązania. Pamiętaj, aby zwolnić powiązanie, gdy tylko przestanie być potrzebne.

Typowe powiązania i sprawdzone metody:

  • Play Integrity API: służy do sprawdzania integralności urządzenia.
    • Sprawdzanie integralności urządzenia po ekranie wczytywania i przed odtwarzaniem multimediów
    • Przed odtworzeniem treści zwolnij odwołania do PlayIntegrity StandardIntegrityManager.
  • Biblioteka płatności w Play: służy do zarządzania subskrypcjami i zakupami za pomocą Google Play.
  • GMS FontsProvider
    • Na urządzeniach z małą ilością pamięci RAM lepiej jest używać samodzielnych czcionek niż dostawcy czcionek, ponieważ pobieranie czcionek jest kosztowne, a dostawca czcionek będzie wiązać usługi, aby to zrobić.
  • Biblioteka Asystenta Google: czasami używana do wyszukiwania i wyszukiwania w aplikacji. Jeśli to możliwe, zastąp tę bibliotekę.
    • W przypadku aplikacji na telewizory: użyj funkcji zamiany tekstu na mowę Gboard lub biblioteki androidx.leanback.
    • W przypadku aplikacji napisanych w Compose:
      • Użyj funkcji zamiany tekstu na mowę w Gboard, aby wdrożyć wyszukiwanie głosowe.
    • Wdróż funkcję Obejrzyj następny, aby ułatwić odkrywanie treści multimedialnych w aplikacji.

Usługi działające na pierwszym planie

Usługi na pierwszym planie to specjalny rodzaj usługi, która jest powiązana z powiadomieniem. To powiadomienie jest wyświetlane na pasku powiadomień na telefonach i tabletach, ale urządzenia TV nie mają paska powiadomień w takim samym sensie jak te urządzenia. Nawet jeśli usługi działające na pierwszym planie są przydatne, ponieważ mogą działać, gdy aplikacja jest w tle, aplikacje na telewizory muszą przestrzegać tych wytycznych:

Na Androidzie TV i Google TV usługi działające na pierwszym planie mogą działać po opuszczeniu aplikacji przez użytkownika tylko w tych przypadkach:

  • W przypadku aplikacji audio: usługi na pierwszym planie mogą działać po opuszczeniu aplikacji przez użytkownika, aby odtwarzać ścieżkę audio. Usługa musi zostać natychmiast zatrzymana po zakończeniu odtwarzania dźwięku.
  • W przypadku innych aplikacji: wszystkie usługi działające na pierwszym planie muszą zostać zatrzymane, gdy użytkownik opuści aplikację, ponieważ nie ma powiadomienia, które informowałoby użytkownika, że aplikacja nadal działa i zużywa zasoby.
  • W przypadku zadań w tle, takich jak aktualizowanie rekomendacji lub Warte obejrzenia, używaj WorkManager.

Zadania i alarmy

WorkManager to najnowocześniejszy interfejs API Androida do planowania cyklicznych zadań w tle. WorkManager będzie używać nowego interfejsu JobScheduler, gdy będzie on dostępny (SDK w wersji 23 lub nowszej), a starego interfejsu AlarmManager, gdy nie będzie dostępny. Aby poznać sprawdzone metody wykonywania zaplanowanych zadań na telewizorze, zapoznaj się z tymi zaleceniami:

  • Unikaj używania interfejsów API AlarmManager w pakietach SDK w wersji 23 lub nowszej, zwłaszcza interfejsów AlarmManager.set(), AlarmManager.setExact() i podobnych metod, ponieważ nie pozwalają one systemowi określić odpowiedniego czasu na uruchomienie zadań (np. gdy urządzenie jest w stanie bezczynności).
  • Na urządzeniach z małą ilością pamięci RAM unikaj uruchamiania zadań, chyba że jest to absolutnie konieczne. W razie potrzeby używaj WorkManagera WorkRequest tylko do aktualizowania rekomendacji po odtworzeniu i staraj się to robić, gdy aplikacja jest jeszcze otwarta.
  • Zdefiniuj WorkManager Constraints, aby system uruchamiał zadania w odpowiednim czasie:

Kotlin

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()

Java

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()
  • Jeśli musisz regularnie uruchamiać zadania (np. aby aktualizować sekcję Obejrzyj następny na podstawie aktywności użytkownika związanej z oglądaniem treści w aplikacji na innym urządzeniu), utrzymuj zużycie pamięci na niskim poziomie, tak aby wynosiło poniżej 30 MB.

Ogólne uwagi dotyczące pamięci

Poniższe wytyczne zawierają ogólne informacje o tworzeniu aplikacji na Androida:

  • Minimalizuj przydzielanie obiektów, optymalizuj ponowne używanie obiektów i szybko zwalniaj nieużywane obiekty.
    • Nie przechowuj odwołań do obiektów, zwłaszcza map bitowych.
    • Unikaj używania wywołań System.gc() i bezpośrednich wywołań zwalniania pamięci, ponieważ zakłócają one proces obsługi pamięci przez system. Na przykład na urządzeniach korzystających z zRAM wymuszone wywołanie gc() może tymczasowo zwiększyć wykorzystanie pamięci z powodu kompresji i dekompresji pamięci.
    • Używaj LazyList, jak pokazano w przeglądarce katalogu w bibliotece Compose lub RecyclerView w wycofanym już zestawie narzędzi Leanback UI, aby ponownie wykorzystywać widoki i nie tworzyć ponownie elementów listy.
    • Lokalnie zapisuj w pamięci podręcznej elementy odczytywane od zewnętrznych dostawców treści, które prawdopodobnie nie ulegną zmianie, i określaj interwały aktualizacji, które zapobiegają przydzielaniu dodatkowej pamięci zewnętrznej.
  • Sprawdź, czy nie występują wycieki pamięci.
    • Uważaj na typowe przypadki wycieku pamięci, takie jak odwołania w wątkach anonimowych, ponowne przydzielanie buforów wideo, które nigdy nie są zwalniane, i inne podobne sytuacje.
    • Użyj zrzutu sterty, aby debugować wycieki pamięci.
  • Generuj profile podstawowe, aby zminimalizować ilość kompilacji w momencie, gdy aplikacja jest uruchamiana „na zimno”.

Informacje o bezpośrednim odzyskiwaniu pamięci

Gdy aplikacja na Androida TV zażąda pamięci, a system będzie pod presją, jądro systemu Linux, na którym opiera się Android, może być zmuszone do użycia bezpośredniego odzyskiwania pamięci.

Proces ten polega na całkowitym wstrzymaniu wątku przydzielającego w celu oczekiwania na zwolnione strony pamięci. Dzieje się tak, gdy odzyskiwanie w tle nie jest w stanie proaktywnie utrzymywać wystarczającej puli pamięci.

Może to powodować wyraźne przerwy lub zacinanie się, ponieważ system wstrzymuje przydzielanie wątków, dopóki nie będzie dostępna wystarczająca ilość pamięci. W tym sensie przydzielanie wątków nie ogranicza się do wywołań kodu aplikacji, takich jak malloc(). Pamięć musi być przydzielana na przykład do strony w stronach kodu.

Podsumowanie narzędzi