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 zamykania aplikacji z braku pamięci (LMK) może reagować na duże obciążenie pamięci przez zamykanie najmniej istotnych procesów. Następnie te komponenty mogą wkrótce się ponownie uruchomić, 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 pamięci, tymczasowo wstrzymując wykonywanie wątku podczas oczekiwania na alokację pamięci. Może to dotyczyć dowolnego wątku, np. głównego wątku lub wątków związanych z kodekiem, i 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 multimediów, 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 aplikację 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:

  • Anonimowa + przestrzeń wymiany: obejmuje pamięć przydzieloną na potrzeby Javy, kodu natywnego i 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 alokacji, 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 tworzyć kompozycje obrazów 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 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: do obsługi zasobów audio i wideo używaj wywołań zwrotnych cyklu życia aktywności. Jeśli Twoja aplikacja nie jest aplikacją audio, zatrzymaj odtwarzanie, gdy w aktywnościach wystąpi onStop, zapisz wszystkie wykonywane czynności i zwolnij zasoby. Aby zaplanować pracę na 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 blokuj połączeń sieciowych podczas uruchamiania aplikacji. Wydłużają one czas uruchamiania aplikacji i zwiększają 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 wyślij żądania sieciowe.

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 obecnie wycofanym 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