Ottimizzazione dell'utilizzo della memoria

L'ottimizzazione della memoria è fondamentale per garantire un funzionamento regolare, per evitare arresti anomali delle app e per mantenere la stabilità del sistema e l'integrità della piattaforma. Sebbene l'utilizzo della memoria debba essere monitorato e ottimizzato in ogni app, le app di contenuti per TV devono affrontare sfide specifiche diverse da quelle delle app Android tradizionali per dispositivi portatili.

Un elevato consumo di memoria può causare problemi con i comportamenti delle app e del sistema, tra cui:

  • L'app stessa può diventare lenta o presentare ritardi oppure, nel peggiore dei casi, essere interrotta.
  • I servizi di sistema visibili all'utente (Controllo del volume, dashboard Impostazioni immagine, Assistente vocale e così via) diventano molto lenti o potrebbero non funzionare affatto.
  • I componenti di sistema possono essere interrotti, quindi riavviarsi, attivando picchi di contesa delle risorse estreme e incidendo direttamente sull'app in primo piano.
  • La transizione all'avvio può essere notevolmente ritardata e l'app in primo piano può sembrare non rispondere fino al termine della transizione.
  • Il sistema potrebbe entrare in una situazione di recupero diretto, interrompendo temporaneamente l'esecuzione di un thread in attesa dell'allocazione della memoria. Questo può accadere a qualsiasi thread, ad esempio il thread principale o i thread relativi ai codec, potenzalmente causando cali di frame audio e video e glitch dell'interfaccia utente.

Considerazioni sulla memoria sui dispositivi TV

I dispositivi TV in genere hanno una memoria notevolmente inferiore rispetto a smartphone o tablet. Ad esempio, una configurazione che possiamo vedere sulla TV è 1 GB di RAM e risoluzione video 1080p. Allo stesso tempo, la maggior parte delle app per TV ha funzionalità simili, quindi un'implementazione simile e sfide comuni. Queste due situazioni presentano problemi non riscontrati in altri tipi di dispositivi e app:

  • Le app TV multimediali sono in genere composte da visualizzazioni di immagini a griglia e da immagini di sfondo a schermo intero che richiedono il caricamento di molte immagini nella memoria in un breve periodo di tempo.
  • Le app TV riproducono stream multimediali che richiedono l'allocazione di una certa quantità di memoria per riprodurre video e audio e richiedono buffer multimediali considerevoli per garantire una riproduzione fluida.
  • Le funzionalità multimediali aggiuntive (avanzamento, cambio di puntata, cambio di traccia audio e così via) possono richiedere una maggiore pressione sulla memoria se non sono implementate correttamente.

Informazioni sui dispositivi TV

Questa guida si concentra principalmente sull'utilizzo della memoria delle app e sui target di memoria per i dispositivi con poca RAM.

Sui dispositivi TV, prendi in considerazione queste caratteristiche:

  • Memoria del dispositivo: la quantità di memoria ad accesso casuale (RAM) installata sul dispositivo.
  • Risoluzione dell'interfaccia utente del dispositivo: la risoluzione utilizzata dal dispositivo per eseguire il rendering dell'interfaccia utente del sistema operativo e delle applicazioni. In genere è inferiore alla risoluzione video del dispositivo.
  • Risoluzione video: la risoluzione massima a cui il dispositivo può riprodurre i video.

Ciò porta alla classificazione di diversi tipi di dispositivi e alla modalità di utilizzo della memoria da parte di questi dispositivi.

Riepilogo dei dispositivi TV

Memoria del dispositivo Risoluzione video del dispositivo Risoluzione dell'interfaccia utente del dispositivo isLowRAMDevice()
1 GB 1080p 720p
1,5 GB 2160p 1080p
≥1,5 GB 1080p 720p o 1080p No*
≥2 GB 2160p 1080p No*

Dispositivi TV con poca RAM

Questi dispositivi si trovano in una situazione di scarsità di memoria e segnaleranno ActivityManager.isLowRAMDevice() come true. Le applicazioni in esecuzione su dispositivi TV con poca RAM devono implementare ulteriori misure di controllo della memoria.

Consideriamo che i dispositivi con le seguenti caratteristiche rientrino in questa categoria:

  • Dispositivi da 1 GB: 1 GB di RAM, risoluzione dell'interfaccia utente 720p/HD (1280 x 720), risoluzione video 1080p/Full HD (1920 x 1080)
  • Dispositivi da 1,5 GB: 1,5 GB di RAM, risoluzione UI 1080p/Full HD (1920x1080), risoluzione video 2160p/Ultra HD/4K (3840x2160)
  • Altre situazioni in cui l'OEM ha definito il ActivityManager.isLowRAMDevice() flag a causa di ulteriori vincoli di memoria.

Dispositivi TV normali

Questi dispositivi non soffrono di una situazione di pressione della memoria così significativa. Consideriamo che questi dispositivi abbiano le seguenti caratteristiche:

  • ≥1,5 GB di RAM, interfaccia utente a 720p o 1080p e risoluzione video 1080p
  • ≥ 2 GB di RAM, interfaccia utente a 1080p e risoluzione video a 1080p o 2160p

Ciò non significa che le app non debbano preoccuparsi dell'utilizzo della memoria su questi dispositivi, poiché alcuni utilizzi impropri specifici della memoria possono comunque esaurire la memoria disponibile e avere prestazioni scadenti.

Target di memoria sui dispositivi TV con poca RAM

Quando misuri la memoria su questi dispositivi, consigliamo vivamente di monitorare ogni sezione della memoria utilizzando il profilatore della memoria di Android Studio. Le app TV devono profilare l'utilizzo della memoria e cercare di mantenere le proprie categorie al di sotto delle soglie che definiamo in questa sezione.

profiler della memoria

Nella sezione Come viene conteggiata la memoria troverete una spiegazione dettagliata dei dati sulla memoria registrati. Per la definizione delle soglie per le app per TV, ci concentreremo su tre categorie di memoria:

  • Anonimo + Scambio: composto da memoria di allocazione Java + nativa + dello stack in Android Studio.
  • Grafica: segnalata direttamente nello strumento di profilazione. Generalmente composto da texture grafiche.
  • File: segnalato come categorie "Codice" + "Altro" in Android Studio.

Con queste definizioni, la tabella seguente indica il valore massimo che deve utilizzare ogni tipo di gruppo di memoria:

Tipo di memoria Finalità Target di utilizzo (1 GB)
Anonimo + scambio (Java + nativo + stack) Utilizzato per allocazioni, buffer multimediali, variabili e altre attività che richiedono molta memoria. < 160 MB
Grafica Utilizzato dalla GPU per le texture e i buffer correlati al display 30-40 MB
File Utilizzato per le pagine di codice e i file in memoria. 60-80 MB

La memoria totale massima (Anon+Swap + Graphics + File) non deve superare quanto segue:

  • 280 MB di utilizzo totale della memoria (Anon+Swap + Graphics + File) per dispositivi con poca RAM da 1 GB.

È fortemente sconsigliato superare:

  • 200 MB di utilizzo della memoria su (Anon+Swap + Graphics).

Memoria file

Come linee guida generali per la memoria basata su file, tieni presente che:

  • In generale, la memoria dei file viene gestita bene dalla gestione della memoria del sistema operativo.
  • Al momento non abbiamo riscontrato che sia una causa principale di pressione sulla memoria.

Tuttavia, quando si utilizza la memoria file in generale:

  • Non includere librerie inutilizzate nella compilazione e, se possibile, utilizza piccoli sottoinsiemi di librerie anziché quelle complete.
  • Non tenere aperti file di grandi dimensioni in memoria e liberali non appena hai finito di utilizzarli.
  • Riduci al minimo le dimensioni del codice compilato per le classi Java e Kotlin, consulta la guida Ridurre le dimensioni, offuscare e ottimizzare l'app.

Consigli specifici per la TV

Questa sezione fornisce consigli specifici per ottimizzare l'utilizzo della memoria sui dispositivi TV.

Memoria grafica

Utilizza formati e risoluzioni delle immagini appropriati.

  • Non caricare immagini con una risoluzione superiore a quella dell'interfaccia utente del dispositivo. Ad esempio, le immagini a 1080p devono essere ridotte a 720p su un dispositivo con interfaccia utente a 720p.
  • Utilizza bitmap basate sull'hardware, se possibile.
    • In librerie come Glide, attiva la funzionalità Downsampler.ALLOW_HARDWARE_CONFIG che è disabilitata per impostazione predefinita. Se attivi questa opzione, eviti di duplicare le bitmap che altrimenti si troverebbero sia nella memoria grafica sia nella memoria anonima.
  • Evita le visualizzazioni intermedie e i rendering di nuovo
    • Questi possono essere identificati con Android GPU Inspector:
    • Nella sezione "Texture", cerca le immagini che rappresentano i passaggi che portano al rendering finale anziché solo gli elementi che li compongono. Si tratta comunemente di un cosiddetto "rendering intermedio".
    • Per le applicazioni SDK per Android, spesso puoi rimuoverle utilizzando il flag di layout forceHasOverlappedRendering:false per disabilitare il rendering intermedio per questo layout.
    • Consulta l'articolo Evitare i rendering in sovrapposizione per una risorsa utile sui rendering in sovrapposizione.
  • Evita di caricare immagini segnaposto, se possibile, utilizza @android:color/ o @color per le texture segnaposto.
  • Evita di comporre più immagini sul dispositivo quando la composizione potrebbe essere eseguita offline. Preferisci caricare immagini autonome anziché comporre immagini dalle immagini scaricate
  • Segui la guida sulla gestione delle bitmap per gestire meglio le bitmap.

Memoria anonimo+scambio

Anon+Swap è composto da allocazioni native + Java + stack nel profiler della memoria di Android Studio. Utilizza ActivityManager.isLowMemoryDevice() per verificare se il dispositivo ha limitazioni di memoria e adattati a questa situazione seguendo queste linee guida.

  • Media:
    • Specifica una dimensione variabile per i buffer multimediali a seconda della RAM del dispositivo e della risoluzione di riproduzione video. Dovrebbe corrispondere a 1 minuto di riproduzione video:
      1. 40-60 MB per 1 GB / 1080p
      2. 60-80 MB per 1,5 GB / 1080p
      3. 80-100 MB per 1,5 GB / 2160p
      4. 100-120 MB per 2 GB / 2160p
    • Libera le allocazioni di memoria per i contenuti multimediali quando modifichi una puntata per evitare aumenti della quantità totale di memoria anonima.
    • Rilascia e interrompi immediatamente le risorse multimediali quando l'app viene interrotta: utilizza i richiami di ciclo di vita dell'attività per gestire le risorse audio e video. Se non sei un'app audio, interrompi la riproduzione quando si verifica onStop() nelle tue attività, salva tutto il lavoro che stai svolgendo e imposta le risorse da rilasciare. Per pianificare il lavoro di cui potresti aver bisogno in un secondo momento. Consulta la sezione Job e sveglie.
    • Fai attenzione alla memoria del buffer durante la ricerca dei video: gli sviluppatori spesso allocano 15-60 secondi aggiuntivi di contenuti futuri quando cercano di avere un video pronto per l'utente, ma questo crea un sovraccarico di memoria aggiuntivo. In genere, non attendere più di 5 secondi prima che l'utente abbia selezionato la nuova posizione del video. Se devi pre-bufferizzare obbligatoriamente un tempo aggiuntivo durante la ricerca, assicurati di:
      • Alloca in anticipo il buffer di ricerca e riutilizzalo.
      • La dimensione del buffer non deve superare i 15-25 MB (a seconda della memoria del dispositivo).
  • Allocazioni:
    • Utilizza le linee guida per la memoria grafica per assicurarti di non duplicare le immagini nella memoria anonima.
      • Le immagini sono spesso le maggiori utilizzatrici di memoria, quindi la loro duplicazione può mettere a dura prova il dispositivo. Questo è particolarmente vero durante la navigazione intensa delle visualizzazioni in griglia di immagini.
    • Libera le allocazioni eliminando i relativi riferimenti quando passi da una schermata all'altra: assicurati che non siano presenti riferimenti a bitmap e oggetti.
  • Raccolte:
    • Esegui il profiling delle allocazioni di memoria dalle librerie quando ne aggiungi di nuove, poiché potrebbero caricare anche librerie aggiuntive, che potrebbero anche eseguire allocazioni e creare Binding.
  • Networking:
    • Non eseguire chiamate di rete bloccanti durante l'avvio dell'app, in quanto rallentano il tempo di avvio dell'applicazione e creano un overhead di memoria aggiuntivo al momento del lancio, quando la memoria è particolarmente limitata dal carico dell'app. Mostra prima una schermata di caricamento o una schermata iniziale ed effettua le richieste di rete una volta che l'UI è in funzione.

Associazioni

Le associazioni introducono un carico aggiuntivo sulla memoria perché caricano altre applicazioni in memoria o aumentano il consumo di memoria dell'app associata (se è già in memoria) per facilitare la chiamata all'API. Di conseguenza, si riduce la memoria disponibile per l'app in primo piano. Quando esegui il binding di un servizio, tieni presente quando e per quanto tempo lo utilizzi. Assicurati di rilasciare il vincolo non appena non è più necessario.

Eseguire il binding a elementi comuni e best practice:

  • API Play Integrity: utilizzata per verificare l'integrità del dispositivo.
    • Controlla l'integrità del dispositivo dopo la schermata di caricamento e prima della riproduzione dei contenuti multimediali
    • Rilascia i riferimenti a PlayIntegrity StandardIntegrityManager prima di riprodurre i contenuti.
  • Play Billing Library: utilizzata per gestire gli abbonamenti e gli acquisti effettuati tramite Google Play.
  • GMS FontsProvider
    • Preferisci utilizzare caratteri autonomi sui dispositivi con poca RAM anziché utilizzare un fornitore di caratteri, poiché il download dei caratteri è costoso e FontsProvider legherà i servizi per farlo.
  • Libreria Assistente Google: a volte viene utilizzata per la ricerca e la ricerca in-app. Se possibile, sostituisci questa libreria.
    • Per le app leanback: utilizza la sintesi vocale di Gboard o la libreria androidx.leanback.
      • Segui le linee guida per la Ricerca per implementare la ricerca.
      • Nota: leanback è deprecato e le app devono passare a TV Compose.
    • Per le app Compose:
      • Utilizza la funzionalità di sintesi vocale di Gboard per implementare la ricerca vocale.
    • Implementa Guarda successivo per rendere rilevabili i contenuti multimediali nella tua app.

Servizi in primo piano

I servizi in primo piano sono un tipo speciale di servizio associato a una notifica. Questa notifica viene visualizzata nella barra delle notifiche su smartphone e tablet, ma i dispositivi TV non dispongono di una barra delle notifiche nello stesso senso. Anche se i servizi in primo piano sono utili perché possono essere mantenuti in esecuzione mentre l'applicazione è in background, le app per TV devono seguire queste linee guida:

In Android TV e Google TV, i servizi in primo piano sono consentiti solo per continuare a funzionare quando l'utente esce dall'app:

  • Per le app audio: è consentito continuare a eseguire i servizi in primo piano solo quando l'utente esce dall'app per continuare a riprodurre la traccia audio. Il servizio deve essere interrotto immediatamente al termine della riproduzione audio.
  • Per qualsiasi altra app: tutti i servizi in primo piano devono essere interrotti quando l'utente esce dall'app, in quanto non viene inviata alcuna notifica per informare l'utente che l'app è ancora in esecuzione e sta consumando risorse.
  • Per i job in background, come l'aggiornamento dei consigli o della sezione Guarda successivo, utilizza WorkManager.

Job e sveglie

WorkManager è l'API Android all'avanguardia per la pianificazione di job ricorrenti in background. WorkManager utilizzerà il nuovo JobScheduler se disponibile (SDK 23 e versioni successive) e il vecchio AlarmManager se non lo è. Per le best practice per l'esecuzione di job pianificati sulla TV, segui questi consigli:

  • Evita di utilizzare le API AlarmManager su SDK 23 e versioni successive, in particolare AlarmManager.set(), AlarmManager.setExact() e metodi simili, in quanto non consentono al sistema di decidere il momento opportuno per eseguire i job (ad esempio, quando il dispositivo è inattivo).
  • Sui dispositivi con poca RAM, evita di eseguire job, a meno che non sia strettamente necessario. Se necessario, utilizza WorkManager WorkRequest solo per aggiornare i consigli dopo la riproduzione e prova a farlo mentre l'app è ancora aperta.
  • Definisci WorkManager Constraints per consentire al sistema di eseguire i job al momento opportuno:

Kotlin

Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
.build()

Java

Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
.build()
  • Se devi eseguire regolarmente job (ad esempio per aggiornare Guarda successivo in base all'attività di visualizzazione dei contenuti di un utente nella tua app su un altro dispositivo), limita l'utilizzo della memoria mantenendo il consumo di memoria del job al di sotto di 30 MB.

Altre linee guida generali

Le seguenti linee guida forniscono informazioni generali sullo sviluppo di app per Android:

  • Riduci al minimo le allocazioni degli oggetti, ottimizza il riutilizzo degli oggetti e dealloca tempestivamente gli oggetti non utilizzati.
    • Non conservare riferimenti a oggetti, in particolare bitmap.
    • Evita di utilizzare System.gc() e le chiamate di rilascio diretto della memoria poiché interferiscono con il processo di gestione della memoria del sistema. Ad esempio, nei dispositivi che utilizzano zRAM, una chiamata forzata a gc() può aumentare temporaneamente l'utilizzo della memoria a causa della compressione e decompressione della memoria.
    • Utilizza LazyList, come mostrato in un browser di cataloghi in Componi o RecyclerView nel toolkit UI Leanback ora deprecato per riutilizzare le visualizzazioni e non ricreare elementi dell'elenco.
    • Memorizza nella cache localmente gli elementi letti da fornitori di contenuti esterni che difficilmente subiranno modifiche e definisci intervalli di aggiornamento che impediscano l'allocazione di memoria esterna aggiuntiva.
  • Controlla la presenza di possibili perdite di memoria.
    • Fai attenzione ai casi tipici di perdita di memoria, come i riferimenti all'interno di thread anonimi, la riallocazione di buffer video che non vengono mai rilasciati e altre situazioni simili.
    • Utilizza il dump dell'heap per eseguire il debugging delle perdite di memoria.
  • Genera profili di riferimento per ridurre al minimo la quantità di compilazione just-in-time necessaria durante l'esecuzione dell'app con avvio a freddo.

Riepilogo degli strumenti