Ottimizzazione dell'utilizzo della memoria

L'ottimizzazione della memoria è fondamentale per garantire prestazioni fluide, evitare arresti anomali delle app e 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 presentano sfide specifiche diverse dalle tipiche app per Android per dispositivi portatili.

Un consumo elevato di memoria può causare problemi con il comportamento di app e sistema, tra cui:

  • L'app stessa può diventare lenta o presentare ritardi oppure, nel peggiore dei casi, essere chiusa.
  • I servizi di sistema visibili all'utente (controllo del volume, dashboard delle impostazioni dell'immagine, assistente vocale e così via) diventano molto lenti o potrebbero non funzionare affatto.
  • Il processo del daemon LMK (low memory killer) potrebbe reagire a un'elevata pressione sulla memoria interrompendo i processi meno essenziali; poi questi componenti potrebbero riavviarsi poco dopo, causando picchi di ulteriore contesa delle risorse che possono influire direttamente sull'app in primo piano.
  • La transizione all'Avvio app può essere notevolmente ritardata e lasciare l'app in primo piano che sembra non rispondere fino al termine della transizione.
  • Il sistema potrebbe iniziare a utilizzare il recupero diretto, mettendo temporaneamente in pausa 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 correlati ai codec, causando potenzialmente cali di frame audio e video e problemi dell'interfaccia utente.

Considerazioni sulla memoria sui dispositivi TV

I dispositivi TV in genere hanno molta meno memoria 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 TV ha funzionalità simili, quindi implementazioni simili 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 flussi multimediali che richiedono l'allocazione di una certa quantità di memoria per riprodurre video e audio e necessitano di buffer multimediali considerevoli per garantire una riproduzione fluida.
  • Funzionalità multimediali aggiuntive (ricerca, cambio di episodio, cambio di traccia audio, ecc.) possono richiedere ulteriore memoria se non implementate correttamente.

Informazioni sui dispositivi TV

Questa guida si concentra principalmente sulla memoria utilizzata dalle app e sugli obiettivi di memoria per i dispositivi con poca RAM.

Sui dispositivi TV, considera 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 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.

Riepilogo 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 memoria limitata e segnaleranno ActivityManager.isLowRAMDevice() come true. Le applicazioni in esecuzione su dispositivi TV con poca RAM devono implementare misure di controllo della memoria aggiuntive.

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

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

Dispositivi TV normali

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

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

Ciò non significa che le app non debbano preoccuparsi dell'utilizzo della memoria su questi dispositivi, in quanto alcuni usi impropri specifici della memoria possono comunque esaurire la memoria disponibile e avere un rendimento scarso.

Target di memoria sui dispositivi TV con poca RAM

Quando misuri la memoria su questi dispositivi, ti consigliamo vivamente di monitorare ogni sezione della memoria utilizzando il profiler di memoria di Android Studio. Le app TV devono profilare la memoria utilizzata e lavorare per inserire le proprie categorie al di sotto delle soglie definite in questa sezione.

Memory Profiler

Nella sezione Come viene conteggiata la memoria troverai una spiegazione dettagliata delle cifre della memoria riportate. Per la definizione delle soglie per le app TV, ci concentreremo su tre categorie di memoria:

  • Anonima + Swap: composta da memoria di allocazione Java + nativa + stack in Android Studio.
  • Grafica: segnalata direttamente nello strumento Profiler. Generalmente composte da texture grafiche.
  • File: segnalato come categorie "Codice" + "Altri" in Android Studio.

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

Tipo di memoria Purpose Target di utilizzo (1 GB)
Anonimo + Swap (Java + nativo + stack) Utilizzata 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 memoria utilizzata totale (Anon+Swap + Graphics + File) per dispositivi con 1 GB di RAM.

È vivamente consigliato di non superare:

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

Memoria file

Come indicazioni 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 delle cause principali della pressione della memoria.

Tuttavia, quando si ha a che fare con la memoria dei file in generale:

  • Non includere librerie inutilizzate nella build e utilizza piccoli sottoinsiemi di librerie anziché quelle complete, se possibile.
  • Non tenere aperti file di grandi dimensioni in memoria e chiudili non appena hai finito di utilizzarli.
  • Riduci al minimo le dimensioni del codice compilato per le classi Java e Kotlin. Consulta la guida Riduci, offusca e ottimizza la tua app.

Consigli specifici per la TV

Questa sezione fornisce consigli specifici per ottimizzare la memoria utilizzata sui dispositivi TV.

Memoria video

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 ridimensionate a 720p su un dispositivo con UI a 720p.
  • Utilizza bitmap basati sull'hardware quando possibile.
    • Nelle librerie come Glide, attiva la funzionalità Downsampler.ALLOW_HARDWARE_CONFIG che è disattivata per impostazione predefinita. L'attivazione di questa opzione evita la duplicazione delle bitmap che altrimenti si troverebbero sia nella memoria video che nella memoria anonima.
  • Evita rendering intermedi e rendering ripetuti
    • Questi possono essere identificati con Android GPU Inspector:
    • Nella sezione "Texture", cerca le immagini che rappresentano i passaggi verso il rendering finale anziché solo gli elementi che lo compongono. Si tratta in genere di un cosiddetto "rendering intermedio".
    • Per le applicazioni SDK Android, spesso puoi rimuoverli utilizzando il flag di layout forceHasOverlappedRendering:false per disattivare i rendering intermedi per questo layout.
    • Consulta la sezione Evitare rendering sovrapposti sui rendering sovrapposti come risorsa utile.
  • Evita di caricare immagini segnaposto quando 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 da quelle scaricate
  • Segui la guida Gestione delle bitmap per gestire meglio le bitmap.

Anon+Swap memory

Anon+Swap è composto da allocazioni Native + Java + Stack nel profiler di memoria di Android Studio. Utilizza ActivityManager.isLowMemoryDevice() per verificare se il dispositivo ha problemi 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. In questo modo, la riproduzione video dovrebbe durare 1 minuto:
      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
    • Allocazioni di memoria per contenuti multimediali senza costi quando si cambia episodio per evitare aumenti della quantità totale di memoria anonima.
    • Rilascia e interrompi immediatamente le risorse multimediali quando l'app viene interrotta: utilizza i callback del ciclo di vita dell'attività per gestire le risorse audio e video. Se non utilizzi un'app audio, interrompi la riproduzione quando onStop() si verifica nelle tue attività, salva tutto il lavoro che stai svolgendo e imposta le risorse in modo che vengano rilasciate. Per pianificare il lavoro di cui potresti aver bisogno in un secondo momento. Consulta la sezione Lavori e sveglie.
    • Presta attenzione alla memoria del buffer durante la ricerca di video: gli sviluppatori spesso allocano 15-60 secondi aggiuntivi di contenuti futuri durante la ricerca per avere il video pronto per l'utente, ma questo crea un sovraccarico di memoria aggiuntivo. In generale, non superare i 5 secondi di buffer futuro finché l'utente non ha selezionato la nuova posizione del video. Se hai assolutamente bisogno di pre-bufferizzare tempo aggiuntivo durante la ricerca, assicurati di:
      • Alloca in anticipo il buffer di ricerca e riutilizzalo.
      • La dimensione del buffer non deve superare 15-25 MB (a seconda della memoria del dispositivo).
  • Allocazioni:
    • Utilizza le indicazioni sulla memoria video per assicurarti di non duplicare le immagini nella memoria anonima
      • Le immagini spesso occupano la maggior parte della memoria, quindi la loro duplicazione può mettere a dura prova il dispositivo. Ciò è particolarmente vero durante la navigazione intensa delle visualizzazioni a griglia delle immagini.
    • Rilascia le allocazioni eliminando i relativi riferimenti quando sposti le schermate: assicurati che non rimangano riferimenti a bitmap e oggetti.
  • Librerie:
    • Allocazioni di memoria del profilo dalle librerie quando ne aggiungi di nuove, in quanto potrebbero caricare anche librerie aggiuntive, che potrebbero anche effettuare allocazioni e creare binding.
  • Networking:
    • Non eseguire chiamate di rete di blocco durante l'avvio dell'app, rallentano il tempo di avvio dell'applicazione e creano un sovraccarico di memoria aggiuntivo all'avvio, dove la memoria è particolarmente limitata dal caricamento dell'app. Mostra prima una schermata di caricamento o iniziale ed esegui le richieste di rete una volta che l'interfaccia utente è a posto.

Associazioni

I binding introducono un overhead di memoria aggiuntivo in quanto caricano in memoria altre applicazioni o aumentano il consumo di memoria dell'app associata (se è già in memoria) per facilitare la chiamata API. Di conseguenza, riduce la memoria disponibile per l'app in primo piano. Quando associ un servizio, fai attenzione a quando e per quanto tempo utilizzi l'associazione. Assicurati di rilasciare il binding non appena non è più necessario.

Binding tipici 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.
  • Libreria Fatturazione Play: utilizzata per gestire abbonamenti e acquisti tramite Google Play
  • GMS FontsProvider
    • Preferisci utilizzare caratteri autonomi su dispositivi con poca RAM anziché utilizzare un fornitore di caratteri, poiché il download dei caratteri è costoso e FontsProvider assocerà i servizi per farlo.
  • Libreria Assistente Google: a volte utilizzata per la ricerca e la ricerca in-app, se possibile, sostituisci questa libreria.
    • Per le app leanback: utilizza la sintesi vocale Gboard o la libreria androidx.leanback.
      • Segui le linee guida per la ricerca per implementare la ricerca.
      • Nota: leanback è ritirato e le app devono passare a TV Compose.
    • Per le app Compose:
      • Utilizza la sintesi vocale di Gboard per implementare la ricerca vocale.
    • Implementa Watch Next per rendere rilevabili i contenuti multimediali nella tua app.

Servizi in primo piano

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

Su Android TV e Google TV, i servizi in primo piano sono consentiti solo per continuare a essere eseguiti dopo che l'utente esce dall'app:

  • Per le app audio: i servizi in primo piano sono consentiti solo per continuare a essere eseguiti una volta che l'utente esce dall'app per continuare a riprodurre la traccia audio. Il servizio deve essere interrotto immediatamente dopo la fine della riproduzione audio.
  • Per qualsiasi altra app: tutti i servizi in primo piano devono essere interrotti. una volta che l'utente esce dall'app, poiché non è presente alcuna notifica per informare l'utente che l'app è ancora in esecuzione e consuma risorse.
  • Per i job in background, come l'aggiornamento dei consigli o dei video Da guardare, utilizza WorkManager.

Lavoro e sveglie

WorkManager è l'API Android all'avanguardia per la pianificazione di job ricorrenti in background. WorkManager utilizzerà il nuovo JobScheduler quando disponibile (SDK 23+) e il vecchio AlarmManager quando 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 o versioni successive, in particolare AlarmManager.set(), AlarmManager.setExact() e metodi simili, in quanto non consentono al sistema di decidere il momento giusto 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 dei job (ad esempio, per aggiornare Watch Next in base all'attività di visualizzazione di contenuti di un utente nella tua app su un altro dispositivo), mantieni basso l'utilizzo della memoria mantenendo il consumo di memoria del job inferiore a 30 MB.

Considerazioni generali sulla memoria

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

  • Riduci le allocazioni di oggetti, ottimizza il riutilizzo degli oggetti e dealloca tempestivamente gli oggetti inutilizzati.
    • Non conservare riferimenti a oggetti, in particolare bitmap.
    • Evita di utilizzare System.gc() e chiamate di rilascio diretto della memoria in quanto interferiscono con la procedura di gestione della memoria del sistema: ad esempio, nei dispositivi che utilizzano zRAM, una chiamata forzata a gc() può aumentare temporaneamente la memoria utilizzata a causa della compressione e della decompressione della memoria.
    • Utilizza LazyList come mostrato in un browser di cataloghi in Compose o RecyclerView in Leanback UI Toolkit, ora ritirato, per riutilizzare le visualizzazioni e non ricreare gli elementi di elenco.
    • Memorizza nella cache locale gli elementi letti dai fornitori di contenuti esterni che è improbabile che cambino e definisci intervalli di aggiornamento che impediscano l'allocazione di memoria esterna aggiuntiva.
  • Controlla la presenza di possibili perdite di memoria.
    • Fai attenzione ai tipici casi di perdita di memoria, ad esempio riferimenti all'interno di thread anonimi, riallocazione di buffer video che non vengono mai rilasciati e altre situazioni simili.
    • Utilizza il dump dell'heap per debuggare le perdite di memoria.
  • Genera profili di base per ridurre al minimo la quantità di compilazione just-in-time necessaria durante l'esecuzione dell'app con avvio a freddo.

Informazioni sul recupero diretto della memoria

Quando un'applicazione Android TV richiede memoria e il sistema è sotto pressione, il kernel Linux, che è alla base di Android, potrebbe dover utilizzare il recupero diretto della memoria.

La procedura prevede la sospensione completa di qualsiasi thread di allocazione per attendere le pagine di memoria liberate. Ciò si verifica quando il recupero in background non è in grado di mantenere in modo proattivo un pool di memoria sufficiente.

Ciò può causare pause o scatti evidenti nell'esperienza utente, poiché il sistema mette in pausa l'allocazione dei thread finché non viene resa disponibile memoria sufficiente. In questo senso, l'allocazione dei thread non è limitata alle chiamate di codice dell'applicazione come malloc(); ad esempio, la memoria deve essere allocata alla pagina nelle pagine di codice.

Riepilogo degli strumenti