Gestire la memoria dell'app

Questa pagina spiega come ridurre in modo proattivo l'utilizzo di memoria all'interno dell'app. Per informazioni su come il sistema operativo Android gestisce la memoria, consulta la Panoramica della gestione della memoria.

La memoria ad accesso casuale (RAM) è una risorsa preziosa per qualsiasi ambiente di sviluppo software ed è ancora più importante per un sistema operativo mobile in cui la memoria fisica è spesso limitata. Sebbene sia il runtime Android (ART) sia la macchina virtuale Dalvik eseguano la raccolta del garbage di routine, ciò non significa che puoi ignorare quando e dove la tua app alloca e rilascia la memoria. Devi comunque evitare di introdurre perdite di memoria, in genere causate dalla conservazione dei riferimenti agli oggetti nelle variabili membro statiche, e rilasciare gli oggetti Reference al momento opportuno, come definito dai callback del ciclo di vita.

Monitorare la memoria disponibile e l'utilizzo della memoria

Devi trovare i problemi di utilizzo della memoria della tua app prima di poterli risolvere. La Memory Profiler in Android Studio ti aiuta a trovare: e diagnosticare i problemi di memoria nei seguenti modi:

  • Scopri in che modo la tua app alloca la memoria nel tempo. Lo strumento di analisi della memoria mostra un grafico in tempo reale della quantità di memoria utilizzata dalla tua app, del numero di oggetti Java allocati e del momento in cui avviene la raccolta dei rifiuti.
  • Avvia gli eventi di raccolta dei rifiuti e acquisisci un'istantanea dell'heap Java durante l'esecuzione dell'app.
  • Registra le allocazioni della memoria dell'app, controlla tutti gli oggetti allocati, visualizza l'analisi dello stack per ogni allocazione e passa al codice corrispondente nell'editor di Android Studio.

Rilasciare memoria in risposta agli eventi

Android può recuperare la memoria dall'app o interromperla del tutto, se necessario, per liberare memoria per le attività critiche, come spiegato nella Panoramica della gestione della memoria. Per ulteriore assistenza bilanciare la memoria di sistema ed evitare che il sistema interrompa il processo dell'app, puoi implementare il ComponentCallbacks2 dell'interfaccia nelle tue Activity classi. Il onTrimMemory() il metodo di callback avvisa l'app di eventi relativi al ciclo di vita o alla memoria che rappresentano l'opportunità di ridurre volontariamente la memoria utilizzata dalla tua app. Liberare memoria potrebbe ridurre la probabilità che la tua app venga interrotta dal task killer per memoria insufficiente.

Puoi implementare il callback onTrimMemory() per rispondere a diversi argomenti come mostrato nell'esempio seguente:

Kotlin

import android.content.ComponentCallbacks2
// Other import statements.

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    override fun onTrimMemory(level: Int) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Java

import android.content.ComponentCallbacks2;
// Other import statements.

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    public void onTrimMemory(int level) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Controllare la quantità di memoria necessaria

Per consentire l'esecuzione di più processi, Android imposta un limite massimo per le dimensioni dell'heap assegnate a ogni app. Il limite esatto delle dimensioni dell'heap varia da un dispositivo all'altro in base alla quantità di RAM complessivamente disponibile. Se l'app raggiunge la capacità heap e tenta di allocare più memoria, il sistema genera OutOfMemoryError.

Per evitare di esaurire la memoria, puoi eseguire una query al sistema per determinare quanto spazio heap è disponibili sul dispositivo attuale. Puoi eseguire una query sul sistema per questa cifra richiamando getMemoryInfo(). Restituisce un ActivityManager.MemoryInfo oggetto che fornisce informazioni sullo stato corrente della memoria del dispositivo, tra cui la memoria disponibile, la memoria totale e la soglia di memoria, ovvero il livello di memoria a cui il sistema inizia a interrompere i processi. Inoltre, l'oggetto ActivityManager.MemoryInfo espone lowMemory, che è un semplice valore booleano che indica se la memoria del dispositivo scarseggia.

Il seguente snippet di codice di esempio mostra come utilizzare il metodo getMemoryInfo() nella tua app.

Kotlin

fun doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    if (!getAvailableMemory().lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return ActivityManager.MemoryInfo().also { memoryInfo ->
        activityManager.getMemoryInfo(memoryInfo)
    }
}

Java

public void doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}

Utilizza costrutti di codice più efficienti in termini di memoria

Alcune funzionalità Android, classi Java e costrutti di codice utilizzano più memoria di altre. Puoi al minimo la quantità di memoria usata dall'app scegliendo alternative più efficienti nel codice.

Utilizza i servizi con parsimonia

Ti consigliamo vivamente di non lasciare i servizi in esecuzione quando non sono necessari. Lasciare in esecuzione servizi non necessari è uno dei peggiori errori di gestione della memoria che un'app per Android possa commettere. Se la tua app necessita di un servizio per funzionare in background, non lasciarla in esecuzione a meno che non debba eseguire un job. Interrompi il servizio una volta completata l'attività. In caso contrario, potresti causare una perdita di memoria.

Quando avvii un servizio, il sistema preferisce mantenere in esecuzione il processo per quel servizio. Questo comportamento rende le procedure di servizio molto costose perché la RAM utilizzata da un servizio rimane non disponibile per altre procedure. Ciò riduce il numero di processi memorizzati nella cache che il sistema può mantenere nella cache LRU, rendendo meno efficiente il passaggio da un'app all'altra. Può anche portare allo schiacciamento nel quando la memoria è limitata e il sistema non è in grado di mantenere processi sufficienti per ospitare tutti i servizi attualmente in esecuzione.

In genere, evita di utilizzare servizi persistenti a causa delle continue richieste che richiedono alla memoria disponibile. Ti consigliamo invece di utilizzare un'implementazione alternativa, come WorkManager. Per ulteriori informazioni su come utilizzare WorkManager per pianificare i processi in background, consulta Lavoro costante.

Utilizza i container di dati ottimizzati

Alcuni corsi forniti dal linguaggio di programmazione non sono ottimizzati per l'uso sui dispositivi mobili dispositivi mobili. Ad esempio, l'implementazione generica di HashMap può essere inefficace in termini di memoria perché richiede un oggetto entry separato per ogni mappatura.

Il framework Android include diversi contenitori di dati ottimizzati, tra cui SparseArray, SparseBooleanArray e LongSparseArray. Ad esempio, le classi SparseArray sono più efficienti perché evitano al sistema di dover autoboxare la chiave e a volte il valore, il che crea un altro paio di oggetti per voce.

Se necessario, puoi sempre passare agli array non elaborati per una struttura di dati snella.

Fai attenzione alle astrazioni del codice

Gli sviluppatori spesso utilizzano le astrazioni come buona prassi di programmazione perché possono migliorare la flessibilità e la manutenzione del codice. Tuttavia, le astrazioni sono molto più costose perché generalmente richiedono più codice da eseguire, richiedendo più tempo e RAM per mappare il codice nella memoria. Se le tue astrazioni non sono significativamente vantaggiose, evitale.

Utilizza protobuf lite per i dati serializzati

Protocollo I buffer (protobuf) sono un meccanismo estensibile e indipendente dal linguaggio e dalla piattaforma progettato Google per la serializzazione di dati strutturati, simili a XML, ma più piccoli, più veloci e più semplici. Se utilizzi i protobuf per i tuoi dati, utilizza sempre i protobuf lite nel codice lato client. I protobuf standard generano codice estremamente dettagliato, che può causare molti problemi nella tua app, ad esempio un aumento dell'utilizzo della RAM, un aumento significativo delle dimensioni dell'APK ed esecuzione più lenta.

Per ulteriori informazioni, consulta il file readme di protobuf.

Evitare il tasso di abbandono della memoria

Gli eventi di raccolta dei rifiuti non influiscono sulle prestazioni dell'app. Tuttavia, molti eventi di raccolta del garbage che si verificano in un breve periodo di tempo possono scaricare rapidamente la batteria e aumentare leggermente il tempo di configurazione dei frame a causa delle interazioni necessarie tra il garbage collector e i thread dell'app. Maggiore è il tempo che il sistema impiega nella raccolta dei rifiuti, più velocemente la batteria drenaggio.

Spesso, il turnover della memoria può causare un numero elevato di eventi di raccolta dei rifiuti. In pratica, il turnover della memoria descrive il numero di oggetti temporanei allocati che si verificano in un determinato periodo di tempo.

Ad esempio, potresti allocare più oggetti temporanei all'interno di un loop for. Oppure potresti creare un nuovo Paint o Bitmap oggetti all'interno dell'elemento onDraw() funzione di una vista. In entrambi i casi, l'app crea molti oggetti rapidamente ad alto volume. Questi può consumare rapidamente tutta la memoria disponibile nella giovane generazione, forzando una garbage collection che si verifichi.

Utilizza il Memory Profiler per trovare i luoghi in il tuo codice in cui il tasso di abbandono della memoria è elevato prima di poterlo risolvere.

Dopo aver identificato le aree problematiche nel codice, prova a ridurre il numero di allocazioni nelle aree critiche per le prestazioni. Valuta la possibilità di spostare gli elementi dagli loop interni o in una struttura di allocazione basata su factory.

Puoi anche valutare se i pool di oggetti sono utili per il caso d'uso. Con un pool di oggetti, anziché inserire un'istanza di oggetto nel pool, la rilasci nel pool quando non è più necessaria. La prossima volta che è necessaria un'istanza di oggetto di quel tipo, puoi acquisirla dal pool anziché che allocarlo.

Valuta attentamente le prestazioni per determinare se un pool di oggetti è adatto in una determinata situazione. In alcuni casi i pool di oggetti potrebbero peggiorare le prestazioni. Anche se i pool evitano allocazioni, inoltre, introducono altre spese generali. Ad esempio, la gestione del pool di solito comporta con un overhead non trascurabile. Inoltre, l'eliminazione dell'istanza dell'oggetto raggruppato per evitare perdite di memoria durante il rilascio e la relativa inizializzazione durante l'acquisizione può avere un overhead non nullo.

Anche il blocco di più istanze di oggetti nel pool del necessario comporta un onere per la raccolta immondizia. Sebbene i pool di oggetti riducano il numero di invocazioni della raccolta dei rifiuti, finiscono per aumentare la quantità di lavoro necessaria per ogni chiamata, poiché è proporzionale al numero di byte attivi (raggiungibili).

Rimuovi risorse e librerie che richiedono molta memoria

Alcune risorse e librerie all'interno del codice possono consumare memoria senza che tu te ne accorga. La le dimensioni complessive dell'app, incluse le librerie di terze parti o le risorse incorporate, possono influire utilizzata dalla tua app. Per migliorare il consumo della memoria dell'app, rimuovi elementi ridondanti, componenti, risorse e librerie del codice inutili o gonfi.

Riduci le dimensioni complessive dell'APK

Puoi ridurre in modo significativo l'utilizzo della memoria da parte dell'app riducendo le dimensioni complessive dell'app. Dimensioni bitmap, risorse, frame di animazione e librerie di terze parti possono contribuire alla dimensione della tua app. Android Studio e l'SDK Android forniscono diversi strumenti per aiutare a ridurre le dimensioni le risorse e le dipendenze esterne. Questi strumenti supportano i moderni metodi di riduzione del codice, come Compilation di R8.

Per ulteriori informazioni su come ridurre le dimensioni complessive dell'app, consulta Riduci le dimensioni dell'app.

Utilizza Hilt o Dagger 2 per l'iniezione di dipendenze

I framework di inserimento delle dipendenze possono semplificare il codice che scrivi e fornire un'esperienza molto utile per i test e altre modifiche alla configurazione.

Se intendi utilizzare un framework di Dependency Injection nella tua app, ti consigliamo di utilizzare Hilt o Dagger. Hilt è un'inserimento di dipendenze libreria per Android basata su Dagger. Dagger non utilizza la riflessione per eseguire la scansione del codice della tua app. Puoi utilizzare l'implementazione statica del tempo di compilazione di Dagger nelle app per Android senza il costo del runtime o l'utilizzo della memoria.

Altri framework di inserimento delle dipendenze che utilizzano la riflessione inizializzano i processi analizzando il tuo per le annotazioni. Questo processo può richiedere molti più cicli della CPU e RAM e può causare un notevole ritardo all'avvio dell'app.

Fai attenzione a utilizzare librerie esterne

Il codice libreria esterna spesso non è scritto per ambienti mobili e può essere inefficiente per lavorare con un client mobile. Quando usi una libreria esterna, potresti doverne ottimizzare libreria per dispositivi mobili. Pianifica questo lavoro in anticipo e analizza la libreria in termini di dimensione del codice e impronta RAM prima di utilizzarla.

Anche alcune librerie ottimizzate per il mobile possono causare problemi a causa di implementazioni diverse. Ad esempio, una libreria potrebbe utilizzare protobuf lite, mentre un'altra utilizza protobuf micro, con il risultato di due implementazioni protobuf diverse nella tua app. Questo può accadere con implementazioni diverse di logging, analisi, framework di caricamento delle immagini, memorizzazione nella cache e molte altre cose che non ti aspetti.

Sebbene ProGuard possa aiutarti a rimuovere API e risorse con i flag corretti, non può rimuovere le dipendenze interne di grandi dimensioni di una libreria. Le funzionalità che vuoi includere in queste librerie potrebbero richiedere dipendenze di livello inferiore. Questo diventa particolarmente problematico quando utilizzi una sottoclasse Activity di una libreria, che può avere un'ampia gamma di dipendenze, quando le librerie utilizzano la riflessione, che è un'operazione comune e richiede la modifica manuale di ProGuard per funzionare.

Evita di utilizzare una libreria condivisa solo per una o due funzionalità su decine. Non importare una grande quantità di codice e overhead che non utilizzi. Quando decidi se utilizzare una libreria, cerca un'implementazione che corrisponda perfettamente alle tue esigenze. Altrimenti, puoi decidere di creare i tuoi la propria implementazione.