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 prowadzić do problemów z działaniem aplikacji i systemu, takich jak:

  • 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 (regulacja 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. Te komponenty mogą następnie wkrótce zostać ponownie uruchomione, co spowoduje dalsze skoki konkurencji o zasoby, które mogą bezpośrednio wpływać na aplikację działającą na pierwszym planie.
  • Przejście do Launchera może się znacznie opóźnić, 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 to dotyczyć dowolnego wątku, np. wątku głównego 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 i 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 koncentruje się głównie na wykorzystaniu pamięci przez aplikacje i docelowych wartościach pamięci na urządzeniach z małą ilością pamięci RAM.

W przypadku urządzeń TV 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 sposobu, w jaki powinny one wykorzystywać pamięć.

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: 1 GB pamięci RAM, rozdzielczość interfejsu 720p/HD (1280x720), rozdzielczość wideo 1080p/FullHD (1920x1080).
  • 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.

Limity pamięci na urządzeniach z Androidem TV o małej ilości pamięci RAM

Podczas pomiaru pamięci na tych urządzeniach zdecydowanie zalecamy monitorowanie każdego jej obszaru 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 profilującym. 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)
Anonimowa + przestrzeń wymiany (Java + natywna + stos) Używana do przydzielania pamięci, buforów multimediów, zmiennych i innych zadań wymagających dużej ilości pamięci. < 160 MB
Grafika Używana przez procesor graficzny do tekstur i buforów związanych z wyświetlaniem. 30–40 MB
Plik Używana w przypadku stron kodowych 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 (Anon+Swap + Graphics).

Pamięć pliku

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

  • Pamięć plików jest zwykle 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 bibliotek.
  • 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

Ta sekcja zawiera 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. „wersja pośrednia”.
    • W przypadku aplikacji 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ć 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.

Pamięć anonimowa i przestrzeń wymiany

Anon+Swap to alokacje natywne, Java i sterty 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 działaniach 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 film był gotowy dla użytkownika, ale powoduje to dodatkowe obciążenie pamięci. Zwykle nie należy pobierać więcej niż 5 sekund bufora przyszłości, dopóki użytkownik nie wybierze nowej pozycji w filmie. Jeśli podczas przewijania musisz wstępnie buforować dodatkowy czas, 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 również wczytywać dodatkowe biblioteki, które mogą również 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świetl ekran ładowania lub ekran powitalny, a potem wysyłaj żądania sieciowe, gdy interfejs użytkownika będzie już gotowy.

Powiązania

Powiązania zwiększają obciążenie pamięci, ponieważ wczytują 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 nie będzie już 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 interfejsu 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 używać samodzielnych czcionek niż dostawcy czcionek, ponieważ pobieranie czcionek jest kosztowne, a dostawca czcionek będzie wiązać usługi w celu ich pobrania.
  • 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 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 działające na pierwszym planie mogą działać po opuszczeniu aplikacji przez użytkownika tylko w celu odtwarzania ścieżki 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. 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 WorkManageraWorkRequest 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 bitmap.
    • 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 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 odczytane 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 bazowe, aby zminimalizować ilość kompilacji w momencie wykonywania aplikacji podczas uruchamiania „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 pamięci 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 stron w stronach kodu.

Podsumowanie narzędzi