Gestire la memoria dell'app

In questa pagina viene spiegato come ridurre proattivamente la memoria utilizzata all'interno della tua app. Per informazioni su la modalità di gestione della memoria da parte del sistema operativo Android, vedi Panoramica della gestione della memoria.

La memoria ad accesso casuale (RAM) è una risorsa preziosa per qualsiasi ambiente di sviluppo software. ma è ancora più utile per un sistema operativo mobile in cui la memoria fisica è spesso limitata. Sebbene sia la macchina virtuale Android Runtime (ART) sia la macchina virtuale Dalvik eseguano la garbage di routine Ciò non significa che puoi ignorare quando e dove l'app alloca e rilascia la memoria. Devi comunque evitare di introdurre perdite di memoria, di solito causate dal mantenimento dell'oggetto riferimenti nelle variabili membro statiche e rilascia eventuali Reference oggetto in il momento giusto, in base ai callback del ciclo di vita.

Monitorare la memoria disponibile e l'utilizzo della memoria

Prima di poterli risolvere, devi individuare i problemi di utilizzo della memoria dell'app. 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. Memory Profiler mostra un grafico in tempo reale la quantità di memoria utilizzata dalla tua app, il numero di oggetti Java allocati e la .
  • Avvia eventi di garbage collection e acquisisci uno snapshot dell'heap Java mentre usi l'app viene eseguito.
  • 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.

Rilascia memoria in risposta agli eventi

Android può recuperare memoria dalla tua app o interromperla completamente se necessario per liberare memoria per le attività più importanti, come spiegato 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() consente all'app di ascoltare gli eventi correlati alla memoria quando si trova in in primo piano o sullo sfondo. Consente quindi all'app di rilasciare oggetti in risposta al ciclo di vita dell'app eventi di sistema che indicano che il sistema deve recuperare memoria.

Puoi implementare il callback onTrimMemory() per rispondere a diversi problemi di memoria 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) {

        // Determine which lifecycle or system event is raised.
        when (level) {

            ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */
            }

            else -> {
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
            }
        }
    }
}

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) {

        // Determine which lifecycle or system event is raised.
        switch (level) {

            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */

                break;

            default:
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
                break;
        }
    }
}

Controllare la quantità di memoria necessaria

Per consentire più processi in esecuzione, Android imposta un limite rigido alla dimensione heap assegnata per ogni dell'app. Il limite esatto delle dimensioni dell'heap varia da un dispositivo all'altro in base alla quantità di RAM disponibile sul dispositivo nel complesso. 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(). Questo restituisce un ActivityManager.MemoryInfo che fornisce informazioni sullo stato attuale della memoria del dispositivo, incluse informazioni memoria totale, memoria totale e soglia di memoria, ovvero il livello di memoria al quale il sistema di interruzione dei processi. Inoltre, l'oggetto ActivityManager.MemoryInfo espone lowMemory, che è un semplice valore booleano che indica se la memoria del dispositivo scarseggia.

Il seguente esempio di snippet di codice mostra come utilizzare il metodo getMemoryInfo() in la 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;
}

Usa 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. Non necessario di servizi in esecuzione è uno dei peggiori errori di gestione della memoria che un'app per Android può commettere. Se la tua app necessita di un servizio per funzionare in background, non uscire e non è in esecuzione, a meno che non sia necessario eseguire un job. Interrompi il servizio una volta completata l'attività. Altrimenti, potresti causare una perdita di memoria.

Quando avvii un servizio, il sistema preferisce mantenere in esecuzione il processo per quel servizio. Questo questo comportamento rende i processi di servizio molto costosi perché la RAM utilizzata da un servizio rimane non è disponibile per altri processi. In questo modo si 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 generale, evita di utilizzare servizi permanenti a causa delle richieste continue che sottopongono a disponibilità la memoria. 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 dell'HashMap può essere memoria è inefficiente perché necessita di 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 le classi necessario casella automatica la chiave e talvolta il valore, il che crea un paio di altri oggetti per voce.

Se necessario, puoi sempre passare agli array non elaborati per una struttura dei dati agile.

Fai attenzione alle astrazioni del codice

Gli sviluppatori spesso usano le astrazioni come buona pratica di programmazione perché possono migliorare il codice flessibilità e manutenzione. Tuttavia, le astrazioni sono molto più costose perché di solito richiedono più codice da eseguire, che richiede più tempo e RAM per il codice in memoria. Se le astrazioni non sono molto utili, 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 protobuf per i dati, usa sempre protobuf lite nel codice lato client. Regolare I protobuf generano codice estremamente dettagliato, il che può causare molti problemi alla tua app, ad esempio: aumento dell'utilizzo della RAM, aumento significativo delle dimensioni degli APK ed esecuzione più lenta.

Per ulteriori informazioni, consulta protobuf leggimi.

Evitare il tasso di abbandono della memoria

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

Spesso, l'abbandono della memoria può causare un numero elevato di eventi di garbage collection. Nella il tasso di abbandono della memoria descrive il numero di oggetti temporanei allocati che si verificano per un periodo di tempo limitato.

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 del codice, prova a ridurre il numero di allocazioni all'interno in aree critiche per le prestazioni. Prova a spostare gli elementi fuori dai loop interni o magari a spostarli in una fabbrica struttura di allocazione.

Puoi anche valutare se i pool di oggetti sono utili per il caso d'uso. Con un pool di oggetti, invece di facendo cadere un'istanza di oggetto sul pavimento, la rilasci in un 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, se cancelli l'istanza dell'oggetto in pool evitare fughe di memoria durante il rilascio e la sua inizializzazione durante l’acquisizione può avere un valore diverso da zero overhead.

Anche il blocco di più istanze di oggetti del pool del necessario comporta un carico di lavoro eccessivo . Sebbene i pool di oggetti riducano il numero di chiamate alla garbage collection, finiscono per aumentando la quantità di lavoro necessario per ogni chiamata, in quanto è proporzionale al numero di byte live (raggiungibili).

Rimuovi risorse e librerie che utilizzano molta memoria

Alcune risorse e librerie all'interno del codice possono consumare memoria a tua insaputa. 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 della tua app.

Usa Hilt o Dagger 2 per l'inserimento delle 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 inserimento delle dipendenze nella tua app, valuta la possibilità di usare Hilt o Pugnale. Hilt è un'inserimento di dipendenze libreria per Android basata su Dagger. Dagger non utilizza il riflesso per analizzare lo le API nel tuo codice. 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.

Presta attenzione all'utilizzo di 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 questa operazione in anticipo e analizza la libreria in termini di: dimensioni del codice e spazio di archiviazione RAM prima di utilizzarlo.

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

ProGuard può aiutare a rimuovere API e risorse con con i flag giusti, non è possibile rimuovere le grandi dipendenze interne di una libreria. Le funzionalità che vuoi queste librerie potrebbero richiedere dipendenze di livello inferiore. Questo diventa particolarmente problematico quando usa una sottoclasse Activity da un libreria, che può avere ampie fasce di dipendenze, quando le librerie usano la riflessione, che è comune e richiede la modifica manuale di ProGuard per farlo funzionare.

Evita di utilizzare una libreria condivisa solo per una o due funzionalità su decine. Non tirare una grossa fetta quantità di codice e overhead che non utilizzi. Quando valuti se utilizzare una libreria, cerca un'implementazione che soddisfi le tue esigenze specifiche. Altrimenti, puoi decidere di creare i tuoi la propria implementazione.