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 siatce i obrazó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.

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.
- W bibliotekach takich jak Glide włącz funkcję
- 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:falsedo 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@colorw 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:
- 40–60 MB na 1 GB / 1080p
- 60–80 MB w przypadku 1,5 GB / 1080p
- 80–100 MB w przypadku 1,5 GB / 2160p
- 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.- Aby ułatwić sobie obsługę wywołań cyklu życia aktywności, możesz używać komponentów uwzględniających cykl życia, takich jak
LiveDataiLifecycleOwner. - Aby uwzględnić cykl życia w swojej pracy, możesz też używać współprogramów Kotlin i przepływów Kotlin.
- Aby ułatwić sobie obsługę wywołań cyklu życia aktywności, możesz używać komponentów uwzględniających cykl życia, takich jak
- 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).
- 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:
- 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.
- Skorzystaj z wskazówek dotyczących pamięci graficznej, aby mieć pewność, że nie powielasz obrazów w pamięci anonimowej.
- 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.
- Zainicjuj bibliotekę po ekranie ładowania i wykonaj wszystkie czynności związane z płatnościami, zanim zaczniesz odtwarzać multimedia.
- Użyj
BillingClient.endConnection(), gdy skończysz korzystać z biblioteki i zawsze przed odtworzeniem filmu lub multimediów. - Użyj poleceń
BillingClient.isReady()iBillingClient.getConnectionState(), aby sprawdzić, czy usługa została odłączona, jeśli trzeba ponownie wykonać jakieś czynności związane z rozliczeniami, a następnie po zakończeniu ponownie użyj poleceniaBillingClient.endConnection().
- 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.
- Aby zaimplementować wyszukiwanie, postępuj zgodnie z wytycznymi dotyczącymi wyszukiwarki.
- Uwaga: leanback jest wycofany, a aplikacje powinny przejść na TV Compose.
- 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.
- W przypadku aplikacji na telewizory: użyj funkcji zamiany tekstu na mowę Gboard lub biblioteki androidx.leanback.
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
AlarmManagerw pakietach SDK w wersji 23 lub nowszej, zwłaszcza interfejsówAlarmManager.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
WorkRequesttylko 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łaniegc()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 lubRecyclerVieww 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
- Użyj narzędzia profilera pamięci Androida Studio, aby sprawdzić zużycie pamięci podczas korzystania z aplikacji.
- Użyj heapdump, aby sprawdzić konkretne alokacje obiektów i map bitowych.
- Użyj profilera pamięci natywnej, aby sprawdzić alokacje inne niż Java lub Kotlin.
- Użyj Android GPU Inspector, aby sprawdzić przydziały grafiki.