Riduci la memoria

Dopo aver compreso la gestione della memoria di Android e aver configurato gli strumenti per misurare l'utilizzo della memoria del gioco, il passaggio successivo consiste nel ridurla e ottimizzarla attivamente. Rispettare i limiti rigorosi di Android aiuta a impedire al sistema di chiudere il gioco, a ridurre i tempi di avvio lunghi e a garantire che il gioco funzioni correttamente su tutti i dispositivi.

Questa guida fornisce tecniche pratiche per ridurre il footprint della memoria del gioco, concentrandosi in particolare sull'ottimizzazione a livello di asset, sulle configurazioni specifiche del motore e sulle best practice per la gestione della memoria.

Ridurre la memoria in Unity

A causa della progettazione architettonica di Unity, una volta che gli allocatori di blocchi nativi e l'heap gestito si espandono, il motore tende a conservare queste pagine di memoria per il riutilizzo anziché restituirle immediatamente al sistema operativo (OS), anche dopo il rilascio degli asset. In particolare, lo spazio di indirizzi virtuali (memoria riservata) rimane riservato per la durata del processo e la memoria fisica (RSS) non viene recuperata immediatamente fino a quando non si sono verificati diversi cicli di garbage collection (GC) e trimming. Di conseguenza, un picco di memoria temporaneo può far sì che la memoria residente rimanga aumentata a lungo anche dopo la riduzione dell'utilizzo effettivo. Questo comportamento aumenta il rischio di arresti anomali per esaurimento della memoria (OOM) sui dispositivi di fascia bassa e peggiora la stabilità complessiva del runtime.

Pertanto, l'ottimizzazione della memoria di Unity deve essere affrontata attraverso tre pilastri fondamentali adattati a questi comportamenti del motore:

  • Controlla l'utilizzo della memoria attiva per evitare picchi di memoria utilizzata in primo luogo.
  • Gestisci i formati delle texture e le varianti degli shader per assicurarti che gli asset e le risorse native non necessari non vengano istanziati.
  • Esegui il refactoring delle strutture del codice di runtime per eliminare le allocazioni non necessarie nell'heap gestito al fine di ridurre al minimo la frequenza di GC e l'espansione dell'heap.

Per ulteriori informazioni, consulta Ottimizzazione della memoria di Unity.

Ridurre la memoria in Unreal Engine

In Unreal Engine, le pipeline di rendering ad alta fedeltà e i grafici di dipendenza degli oggetti complessi possono aumentare significativamente la pressione della memoria RSS anonima e di quella supportata da file. In particolare, l'utilizzo di riferimenti hard e di gerarchie di ereditarietà di Blueprint profonde forza il caricamento in memoria degli asset connessi inutilizzati. Inoltre, le permutazioni eccessive degli shader, i pool di streaming delle texture non ottimizzati e le tabelle di rilocazione ELF non compresse contribuiscono a un ingombro di memoria di base elevato, aumentando il rischio di terminazioni LMK (Low Memory Killer).

Pertanto, l'ottimizzazione della memoria di Unreal Engine deve essere affrontata attraverso tre pilastri fondamentali adattati a questi comportamenti del motore:

  • Disaccoppia i dati e la logica, sostituendo i riferimenti hard o strong con riferimenti soft o weak.
  • Rimuovi le funzionalità di illuminazione mobile e le opzioni di permutazione inutilizzate per ridurre al minimo gli oggetti di stato della pipeline (PSO) e le destinazioni di rendering ridondanti, applicando la compressione ASTC e personalizzando i pool di streaming delle texture con i profili dei dispositivi.
  • Attiva la compressione della tabella di rilocazione RELR e APS per ridurre le dimensioni del file binario ELF e ridurre il footprint della memoria fisica di runtime.

Per ulteriori informazioni, consulta Ottimizzazione della memoria di Unreal.

Ottimizzazione multiprocesso

La memoria utilizzata dei processi memorizzati nella cache è esclusa dal calcolo dei limiti di memoria perché non ha alcun impatto sulle app attive. L'esecuzione del servizio in un processo isolato separato aiuta il processo principale a passare allo stato memorizzato nella cache il più rapidamente possibile, migliorando così l'integrità del gioco.

Per ulteriori informazioni, consulta Come monitorare lo stato e la memoria dei processi, Come isolare un processo di servizio con Unity, e Come isolare un processo di servizio con Unreal.

Ridurre la memoria utilizzata nei servizi percepiti dall'utente

Il gioco potrebbe dover eseguire la logica in un servizio percepito dall'utente per casi d'uso come il completamento di un download di grandi dimensioni o per i sistemi di chat vocale in background. Queste strategie possono aiutarti a gestire e ridurre la memoria utilizzata in questi scenari.

Strategie per download di grandi dimensioni

Queste strategie potrebbero essere applicate ai download di grandi dimensioni che vuoi continuare anche dopo che l'utente ha ridotto a icona il gioco.

1. Isola il processo di download

Che cosa: assicurati che il sistema operativo possa recuperare immediatamente la memoria non utilizzata dall'app eseguendo il download in un processo separato, poiché è possibile che l'allocatore di memoria mantenga le pagine del pool di memoria e mantenga la memoria RSS anonima artificialmente elevata anche dopo aver liberato gli array. Quando termini o esci esplicitamente dal servizio e dal processo, la memoria viene restituita al pool del sistema operativo e il processo principale rimane inalterato.

  • In Unity: trasferisci il download in un Service Android nativo dichiarato con un processo come android:process=":downloader" in un file manifest personalizzato e richiamalo con l'interfaccia JNI AndroidJavaClass di Unity. Assicurati di terminare il processo al termine del download. Per indicazioni più dettagliate, consulta Eseguire un servizio percepibile in un processo separato con Unity.

  • In Unreal: dichiara un Service Android personalizzato con un processo come android:process=":downloader" utilizzando Unreal Plugin Language e attivalo con l'interfaccia JNI C++. Assicurati di terminare il processo al termine del download. Per indicazioni più dettagliate, consulta Eseguire un servizio percepibile in un processo separato con Unreal.

  • Per Android nativo: dichiara un Service in AndroidManifest con un processo come android:process=":downloader". Esegui il download in questo processo isolato e chiama Process.killProcess(Process.myPid()) al termine del download.

Come aiuta: riduce la durata della memoria e consente di continuare il download rilasciando la memoria utilizzata dal processo principale più grande.

2. Esegui lo streaming dei download direttamente sul disco

Che cosa: esegui lo streaming dei dati direttamente dal socket di rete al disco utilizzando un buffer riutilizzabile di piccole dimensioni e dimensioni fisse anziché accumulare le risposte di rete in un array di grandi dimensioni prima di scriverlo.

  • In Unity: evita di utilizzare DownloadHandlerBuffer per i bundle di asset o i file di grandi dimensioni, poiché alloca un buffer di memoria nativa equivalente alle dimensioni del file size (memoria RSS anonima). Utilizza invece DownloadHandlerFile per eseguire lo streaming dei byte in modo nativo sul disco in un thread in background.

  • In Unreal: invia i blocchi di dati in entrata direttamente a un FArchive (archivio supportato da file che utilizza File Manager di Unreal) con SetResponseBodyReceiveStream() anziché aggiungere payload da un IHttpRequest a un TArray<uint8>.

  • Per Android nativo: invia InputStream a FileOutputStream utilizzando un buffer in pool anziché chiamare .readBytes() o .string() su una risposta HTTP.

Come aiuta: riduce il picco di memoria utilizzata

3. Esegui lo streaming della decompressione dei file scaricati

Che cosa: se il download è compresso, racchiudi lo stream di input di rete in un decompressore di streaming come ZipInputStream anziché scaricare il file, caricarlo nella RAM ed estrarlo.

Come aiuta: riduce il picco di memoria utilizzata

4. Delega al sistema operativo

Che cosa: per evitare di gestire completamente la memoria in background, delega il lavoro alle API native di Android.

  • WorkManager è il wrapper moderno e consigliato per JobScheduler a livello di sistema operativo. Per Android 14 e versioni successive, WorkManager gestisce automaticamente i download attivati dall'utente come job di trasferimento dati avviato dall'utente (UIDT). Viene eseguito all'interno del processo dell'app, quindi devi comunque eseguire lo streaming direttamente sul disco per ridurre al minimo la memoria utilizzata. UIDT protegge l'app dagli arresti anomali per esaurimento della memoria consentendo al sistema operativo di mettere in pausa e riprendere il download in modo graduale se le risorse di sistema diventano limitate.

  • DownloadManager viene eseguito in un processo di sistema separato e non attribuisce la memoria utilizzata per il download all'app. L'app riceverà una notifica di trasmissione quando il file viene scaricato ed è pronto.

Come aiuta: WorkManager aiuta a gestire gli scenari di esaurimento della memoria e DownloadManager riduce la memoria utilizzata dall'app.

Strategie per i servizi ausiliari

Queste strategie potrebbero essere applicate ai servizi ausiliari che un gioco esegue in parallelo con il processo di gioco principale, ad esempio la chat vocale in background.

1. Isola il processo

Che cosa: disaccoppia la funzionalità, ad esempio la soluzione di chat vocale, dal motore grafico principale. Ad esempio, puoi eseguire l'acquisizione del microfono e lo streaming di rete all'interno di un servizio in primo piano Android assegnato a un processo separato (dichiarato nel file manifest, ad esempio con android:process=":voice").

Come aiuta: quando l'app viene ridotta a icona, il processo di gioco principale pesante può passare a uno stato memorizzato nella cache a priorità inferiore, mentre il servizio ausiliario più leggero continua nello stato di servizio percepito dall'utente.

2. Riduci la memoria in-process inutilizzata

Che cosa: se il servizio ausiliario è troppo integrato nel motore di gioco per essere separato, prova a ridurre il più possibile il peso del processo non appena il gioco viene messo in background/ridotto a icona. Valuta la possibilità di svuotare le cache delle texture, scaricare le scene non essenziali, ridurre a 0 i tassi di rendering e di tick del motore e chiamare esplicitamente la garbage collection.

Come aiuta: riduce la memoria utilizzata non necessaria quando il gioco non è in primo piano.