Optymalizacja wykorzystania pamięci

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

Wysokie wykorzystanie pamięci może powodować problemy z działaniem aplikacji i systemu, takie jak:

  • Aplikacja może działać wolno lub z opóźnieniem, a w najgorszym przypadku zostać zamknięta.
  • Usługi systemowe widoczne dla użytkownika (np. kontrola głośności, panel ustawień obrazu, asystent głosowy) działają bardzo wolno lub mogą w ogóle nie działać.
  • Proces demona low memory killer (LMK) może reagować na wysokie obciążenie pamięci przez zabijanie procesów o najmniejszym znaczeniu. Następnie te komponenty mogą zostać ponownie uruchomione, powodując dalsze szczyty rywalizacji o zasoby, co może bezpośrednio wpływać na aplikację na pierwszym planie.
  • Przejście do Launchera może się znacznie opóźnić, a aplikacja na pierwszym planie może wydawać się nieresponsywna do czasu zakończenia przejścia.
  • System może zacząć stosować bezpośrednie odzyskiwanie, tymczasowo wstrzymywać wykonywanie wątków podczas oczekiwania na przydzielenie pamięci. Może się to zdarzyć w przypadku dowolnego wątku, np. głównego lub wątków związanych z kodekiem, co może powodować utraty klatek dźwięku i obrazu oraz błędy interfejsu.

Zagadnienia dotyczące pamięci na urządzeniach TV

Urządzenia telewizyjne mają zwykle znacznie mniej pamięci niż telefony czy tablety. Na przykład konfiguracja, którą widzimy na telewizorze, to 1 GB pamięci RAM i rozdzielczość wideo 1080p. Jednocześnie większość aplikacji na telewizory ma podobne funkcje, a więc podobne wdrożenia i wspólne problemy. Te 2 sytuacje powodują problemy, których nie ma w przypadku innych typów urządzeń i aplikacji:

  • Aplikacje do oglądania multimediów zwykle składają się z widoków obrazów w siatcetła w pełnym ekranie, które wymagają załadowania dużej liczby obrazów do pamięci w krótkim czasie.
  • Aplikacje telewizyjne odtwarzają strumienie multimedialne, które wymagają przydzielenia określonej ilości pamięci na odtwarzanie obrazu i dźwięku, a także dużych buforów multimediów, aby zapewnić płynne odtwarzanie.
  • Dodatkowe funkcje multimediów (przewijanie, zmiana odcinka, zmiana ścieżki audio itp.) mogą wymagać dodatkowej pamięci, jeśli nie są prawidłowo zaimplementowane.

Informacje o telewizorach

Ten przewodnik koncentruje się głównie na wykorzystaniu i docelowym wykorzystaniu pamięci przez aplikacje 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 operacyjnej (RAM) zainstalowanej na urządzeniu.
  • Rozdzielczość interfejsu użytkownika urządzenia: rozdzielczość, której urządzenie używa do renderowania interfejsu systemu operacyjnego i aplikacji; jest ona zwykle niższa niż rozdzielczość wideo urządzenia.
  • Rozdzielczość filmu: maksymalna rozdzielczość, w której urządzenie może odtwarzać filmy.

Prowadzi to do podziału różnych typów urządzeń na kategorie i określenia sposobu wykorzystywania przez nie pamięci.

Podsumowanie dotyczące urządzeń TV

Pamięć urządzenia Rozdzielczość wideo urządzenia Rozdzielczość interfejsu użytkownika 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 z niewielką ilością pamięci RAM

Te urządzenia znajdują się w sytuacji ograniczonej pamięci i będą raportować, że ActivityManager.isLowRAMDevice()ma wartość Prawda. Aplikacje działające na telewizorach z małą ilością pamięci RAM muszą stosować dodatkowe środki kontroli pamięci.

Urządzenia o tych cechach zaliczamy do tej kategorii:

  • Urządzenia z 1 GB pamięci RAM: 1 GB pamięci RAM, interfejs w rozdzielczości 720p/HD (1280 x 720), filmy w rozdzielczości 1080p/FullHD (1920 x 1080).
  • Urządzenia z 1,5 GB pamięci RAM: 1,5 GB pamięci RAM, rozdzielczość interfejsu 1080p/Full HD (1920 x 1080), rozdzielczość wideo 2160p/Ultra HD/4K (3840 x 2160).
  • Inne sytuacje, w których OEM zdefiniował flagę ActivityManager.isLowRAMDevice() ze względu na dodatkowe ograniczenia pamięci.

Standardowe urządzenia telewizyjne

Te urządzenia nie mają problemu z niedostateczną ilością pamięci. Uważamy, że te urządzenia mają te cechy:

  • 1,5 GB pamięci RAM, interfejs w rozdzielczości 720p lub 1080p oraz filmy w rozdzielczości 1080p
  • co najmniej 2 GB pamięci RAM, interfejs w rozdzielczości 1080p oraz filmy w rozdzielczości 1080p lub 2160p.

Nie oznacza to jednak, że aplikacje nie powinny zwracać uwagi na wykorzystanie pamięci na tych urządzeniach, ponieważ niektóre konkretne przypadki nieprawidłowego wykorzystania pamięcimogą prowadzić do wyczerpania dostępnej pamięci i złego działania.

Docelowe wartości 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 wszystkich sekcji pamięci za pomocą profilatora pamięci w Android Studio. Aplikacje na telewizory powinny przeprowadzić profilowanie wykorzystania pamięci i zadbać o to, aby ich kategorie nie przekraczały progów określonych w tej sekcji.

profilowanie pamięci

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

  • Anonimowa + wymiana: składa się z kompozycji Java + Natywna + pamięć alokowana w Android Studio.
  • Grafika: problemy zgłoszone bezpośrednio w narzędziu profilowania. Zwykle składa się z tekstur graficznych.
  • Plik: zgłoszony jako kategoria „Kod” i „Inne” w Android Studio.

W związku z tymi definicjami w tabeli poniżej podano maksymalną wartość, której powinien używać każdy typ grupy pamięci:

Typ pamięci Cel Docelowe wartości wykorzystania (1 GB)
Anonimowy + wymiana (Java + natywny + stos) Służy do alokacji, buforów multimediów, zmiennych i innych zadań wymagających dużej ilości pamięci. < 160 MB
Grafika Służy do obsługi zasobów związanych z teksturami i wyświetlaniem. 30–40 MB
Plik Używany do stron kodu i plików w pamięci. 60–80 MB

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

  • 280 MB łącznego wykorzystania pamięci (Anon+Swap + Graphics + File) na urządzeniach z 1 GB pamięci RAM.

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

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

Pamięć pliku

Ogólne wskazówki dotyczące pamięci używanej do tworzenia kopii zapasowych plików:

  • Ogólnie pamięć plików jest dobrze zarządzana przez system operacyjny.
  • Nie udało nam się na razie ustalić, że jest to główna przyczyna obciążenia pamięci.

Jednak ogólnie w przypadku pamięci pliku:

  • Nie uwzględniaj nieużywanych bibliotek w kompilacji i w miarę możliwości używaj małych podzbiorów bibliotek, a nie wszystkich.
  • Nie zostawiaj otwartych dużych plików w pamięci i zwalniaj je, gdy tylko skończysz z nimi pracować.
  • Zminimalizuj rozmiar skompilowanego kodu w przypadku klas Java i Kotlin. Zapoznaj się z poniższym przewodnikiem: Zmniejsz rozmiar, zaciemnij i zoptymalizuj aplikację.

konkretne rekomendacje dotyczące telewizji,

W tej sekcji znajdziesz konkretne rekomendacje dotyczące optymalizacji wykorzystania pamięci na urządzeniach sterowanych głosem.

Pamięć graficzna

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

  • Nie wczytuj obrazów o wyższej rozdzielczości niż interfejs użytkownika urządzenia. Na przykład obrazy w rozdzielczości 1080p powinny być zmniejszane do rozdzielczości 720p na urządzeniu z interfejsem 720p.
  • Jeśli to możliwe, używaj bitmap obsługiwanych sprzętowo.
    • W przypadku bibliotek 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 byłyby zarówno w pamięci graficznej, jak i w pamięci anonimowej.
  • Unikaj pośrednich renderowań i ponownie renderowania.
    • Można je zidentyfikować za pomocą Android GPU Inspector:
    • W sekcji „Tekstury” znajdź obrazy, które są etapami prowadzącymi do końcowego renderowania, a nie tylko elementami je tworzącymi. Jest to tzw. renderowanie pośrednie.
    • W przypadku aplikacji korzystających z Android SDK można je często usunąć, używając flagi układu forceHasOverlappedRendering:false w celu wyłączenia pośrednich renderów dla tego układu.
    • Zapoznaj się z artykułem Avoid Overlapping Renderings (w języku angielskim) na temat nakładających się renderów, który jest świetnym źródłem informacji.
  • Unikaj wczytywania obrazów zastępczych. W miarę możliwości używaj @android:color/ lub @color do zastępczych tekstur.
  • Unikaj łączenia wielu obrazów na urządzeniu, jeśli kompozycja może zostać wykonana w trybie offline. preferuj wczytywanie samodzielnych obrazów zamiast tworzenia kompozycji z pobranych obrazów;
  • Aby lepiej pracować z pikselowymi obrazami, postępuj zgodnie z przewodnikiem dotyczącym obsługi bitmap.

Anon+Swap memory

Anon+Swap składa się z alokacji natywnych, Javy i komórek stosu w profilu pamięci w Android Studio. Aby sprawdzić, czy urządzenie ma ograniczoną ilość pamięci, użyj opcji ActivityManager.isLowMemoryDevice(). Następnie dostosuj się do tej sytuacji, postępując zgodnie z tymi wskazówkami.

  • Media:
    • Określ zmienny rozmiar buforów multimediów w zależności od pamięci RAM urządzenia i rozdzielczości odtwarzania wideo. Powinien on obejmować 1 minutę odtwarzania filmu:
      1. 40–60 MB na 1 GB / 1080p
      2. 60–80 MB na 1,5 GB / 1080p
      3. 80–100 MB na 1,5 GB / 2160p
      4. 100–120 MB w przypadku 2 GB / 2160p
    • Uwolnij przydziały pamięci multimediów podczas zmiany odcinka, aby zapobiec zwiększaniu łącznej ilości pamięci anonimowej.
    • Natychmiast zwalniaj i zatrzymaj zasoby multimedialne, gdy aplikacja zostanie zatrzymana: do obsługi zasobów audio i wideo używaj funkcji obsługi cyklu życia. Jeśli nie jesteś aplikacją audio, zatrzymaj odtwarzanie, gdy onStop() wystąpi w Twoich działaniach, zapisz całą wykonywaną pracę i ustaw zasoby do zwolnienia. Aby zaplanować zadania, których możesz potrzebować później. Przejdź do sekcji Wątek i alarmy.
    • Podczas przewijania filmu zwracaj uwagę na pamięć buforową: deweloperzy często przeznaczają dodatkowe 15–60 sekund na przyszłe treści, aby film był gotowy do odtworzenia dla użytkownika, ale powoduje to dodatkowe obciążenie pamięci. Ogólnie nie przechodź więcej niż 5 s do przodu, dopóki użytkownik nie wybierze nowej pozycji filmu. Jeśli podczas przewijania musisz z góry buforować dodatkowy czas, pamiętaj o tym, aby:
      • Przydziel bufor przeskakiwania z wyprzedzeniem i używaj go ponownie.
      • 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 na grafikę, aby nie powielać obrazów w pamięci anonimowej.
      • Obrazy często zajmują najwięcej pamięci, więc ich duplikowanie może znacząco obciążać urządzenie. Jest to szczególnie ważne podczas intensywnej nawigacji w widokach siatki obrazów.
    • Zwolnij alokacje, usuwając ich odwołania podczas przenoszenia ekranów: upewnij się, że nie ma żadnych odwołań do bitmap i obiektów.
  • Biblioteki:
    • Profile pamięci z bibliotek podczas dodawania nowych, ponieważ mogą one wczytywać dodatkowe biblioteki, które mogą również dokonywać alokacji i tworzyć wiązania.
  • Nawiązywanie kontaktów:
    • Nie wykonuj blokujących wywołań sieci podczas uruchamiania aplikacji, ponieważ wydłużają one czas uruchamiania aplikacji i wywołują dodatkowe obciążenie pamięci, która jest szczególnie ograniczona przez obciążenie aplikacji. Najpierw wyświetlaj ekran wczytywania lub ekran powitalny, a żądania sieciowe wysyłaj dopiero po wyświetleniu interfejsu użytkownika.

Powiązania

Powiązania wymagają dodatkowej pamięci, ponieważ wprowadzają inne aplikacje do pamięci lub zwiększają zużycie pamięci przez powiązaną aplikację (jeśli jest ona już w pamięci), aby ułatwić wywołanie interfejsu API. W efekcie zmniejsza dostępną pamięć dla aplikacji na pierwszym planie. Podczas wiązania usługi należy pamiętać, kiedy i jak długo jest używane wiązanie. Pamiętaj, aby zwolnić zablokowanie, gdy nie jest już potrzebne.

Typowe wią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 odtworzeniem multimediów
    • Przed odtworzeniem treści udostępnij odwołania do PlayIntegrity StandardIntegrityManager.
  • Biblioteka płatności Google Play: służy do zarządzania subskrypcjami i zakupami w Google Play.
  • GMS FontsProvider
    • Na urządzeniach z małą ilością pamięci RAM lepiej jest używać czcionek samodzielnych niż korzystać z usług dostawcy czcionek, ponieważ pobieranie czcionek jest kosztowne, a FontsProvider będzie wiązać usługi, aby to zrobić.
  • Biblioteka Asystenta Google: czasami służy do wyszukiwania i wyszukiwania w aplikacji. Jeśli to możliwe, zastąp tę bibliotekę.
    • W przypadku aplikacji leanback: użyj funkcji Gboard Text to Speech lub biblioteki androidx.leanback.
      • Aby zaimplementować wyszukiwanie, postępuj zgodnie z wytycznymi dotyczącymi wyszukiwania.
      • Uwaga: interfejs leanback został wycofany, a aplikacje powinny przejść na interfejs TV Compose.
    • W przypadku aplikacji Compose:
      • Aby wdrożyć wyszukiwanie głosowe, użyj funkcji konwersji tekstu na mowę w Gboard.
    • Wprowadź funkcję Obejrzyj następny, aby ułatwić znajdowanie treści multimedialnych w aplikacji.

Usługi działające na pierwszym planie

Usługi na pierwszym planie to specjalny typ usługi powiązanej z powiadomieniem. To powiadomienie wyświetla się na pasku powiadomień na telefonach i tabletach, ale urządzenia telewizyjne nie mają paska powiadomień w takim samym znaczeniu jak te urządzenia. Mimo że usługi na pierwszym planie są przydatne, ponieważ mogą działać, gdy aplikacja jest w tle, aplikacje na telewizory muszą spełniać te wytyczne:

Na Androidzie TV i Google TV usługi działające na pierwszym planie mogą uruchamiać się tylko, gdy użytkownik opuści aplikację:

  • W przypadku aplikacji audio: usługi działające na pierwszym planie mogą działać tylko wtedy, gdy użytkownik opuści aplikację, aby kontynuować odtwarzanie ścieżki audio. Usługa musi zostać natychmiast zatrzymana po zakończeniu odtwarzania dźwięku.
  • W przypadku innych aplikacji: wszystkie usługi na pierwszym planie muszą zostać zatrzymane, gdy użytkownik opuści aplikację, ponieważ nie ma powiadomienia informującego użytkownika, że aplikacja nadal działa i korzysta z zasobów.
  • Do zadań wykonywanych w tle, takich jak aktualizowanie rekomendacji czy Warto obejrzeć, użyj 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 (pakiet SDK 23 lub nowszy), oraz starego AlarmManager, gdy nie będzie. Aby dowiedzieć się, jak najlepiej wykonywać zaplanowane zadania na CTV, postępuj zgodnie z tymi zaleceniami:

  • Unikaj używania interfejsów API AlarmManager w pakiecie SDK 23 i nowszych, zwłaszcza AlarmManager.set(), AlarmManager.setExact() i podobnych metod, ponieważ nie pozwalają one systemowi na określenie odpowiedniego czasu wykonywania zadań (np. gdy urządzenie jest nieaktywne).
  • Na urządzeniach z małą ilością pamięci RAM nie uruchamiaj zadań, chyba że jest to absolutnie konieczne. W razie potrzeby używaj WorkManagera WorkRequest tylko do aktualizowania rekomendacji po zakończeniu odtwarzania.
  • Zdefiniuj WorkManagera Constraints, aby system mógł wykonywać 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 (na przykład aby zaktualizować Następny do obejrzenia na podstawie aktywności związanej z oglądaniem treści w aplikacji na innym urządzeniu), ogranicz zużycie pamięci, utrzymując je poniżej 30 MB.

Ogólne informacje o pamięci

Te wytyczne zawierają ogólne informacje o programowaniu aplikacji na Androida:

  • Zminimalizuj przydziały obiektów, zoptymalizuj ich ponowne użycie i natychmiast oddzielaj 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ń funkcji zwalniającej pamięć, ponieważ zakłócają one proces obsługi pamięci przez system. Na przykład na urządzeniach korzystających z zRAM-u wymuszenie wywołania funkcji gc() może tymczasowo zwiększyć wykorzystanie pamięci z powodu kompresji i dekompresji pamięci.
    • Użyj LazyList, np. w przeglądarce katalogu w sekcji Tworzenie lub RecyclerView w obecnie wycofanym pakiecie narzędzi interfejsu Leanback, aby ponownie wykorzystać widoki zamiast ponownie tworzyć elementy listy.
    • Elementy odczytane od zewnętrznych dostawców treści, które prawdopodobnie się nie zmienią, będą przechowywane w pamięci podręcznej lokalnie, a interwały aktualizacji będą zdefiniowane w sposób, który zapobiegnie przydzielaniu dodatkowej pamięci zewnętrznej.
  • Sprawdź, czy nie występuje wyciek pamięci.
    • Uważaj na typowe przypadki wycieku pamięci, takie jak odwołania w nitkach anonimowych, przydzielanie zasobów buforów wideo, które nigdy nie zostaną zwolnione, i inne podobne sytuacje.
    • Aby debugować wycieki pamięci, użyj zrzutu pamięci sterty.
  • Utwórz profil bazowy, aby zminimalizować ilość kompilacji just-in-time podczas uruchamiania aplikacji „na zimno”.

Odzyskiwanie pamięci bezpośrednio

Gdy aplikacja na Androida TV prosi o pamięć, a system jest obciążony, jądro Linuksa, które jest podstawą Androida, może użyć bezpośredniego odzyskiwania pamięci.

Proces ten polega na całkowitym wstrzymaniu wątku przydzielającego, aby poczekać na zwolnione strony pamięci. Dzieje się tak, gdy mechanizm odzyskiwania pamięci w tle nie jest w stanie utrzymać wystarczającej puli pamięci.

Może to spowodować zauważalne przerwy lub zakłócenia w działaniu aplikacji, ponieważ system wstrzymuje przydzielanie wątków do czasu, aż będzie dostępna wystarczająca ilość pamięci. W tym sensie przydzielanie wątków nie jest ograniczone do wywołań kodu aplikacji, takich jak malloc(). Pamięć musi być na przykład przydzielana stronie w plikach kodu.

Podsumowanie narzędzi