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 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 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.

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.
- 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. „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: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ć 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:
- 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 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.- Możesz używać komponentów uwzględniających cykl życia, takich jak
LiveDataiLifecycleOwner, aby ułatwić sobie obsługę wywołań cyklu życia działania. - Aby uwzględnić cykl życia w swojej pracy, możesz też używać współprogramów Kotlin i przepływów Kotlin.
- 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 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).
- 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 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.
- Zainicjuj bibliotekę po ekranie ładowania i wykonaj wszystkie czynności związane z płatnościami przed odtworzeniem jakichkolwiek multimediów.
- 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, na wypadek gdyby trzeba było ponownie wykonać rozliczenia. Po zakończeniu ponownie użyj poleceniaBillingClient.endConnection().
- 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.
- 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 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
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 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ł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 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 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
- Użyj narzędzia profilera pamięci Android 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.