Best practice di gestione della memoria

Questo documento presuppone che tu abbia seguito le indicazioni delle best practice per le app Android in Gestione della memoria, ad esempio Gestire la memoria dell'app.

Introduzione

Una perdita di memoria è un tipo di perdita di risorse che si verifica quando un programma per computer non rilascia la memoria allocata che non è più necessaria. Una perdita può portare l'applicazione a richiedere al sistema operativo più memoria di quella disponibile, causando quindi l'arresto anomalo dell'applicazione. Una serie di pratiche improprie possono causare perdite di memoria nelle app Android, ad esempio non eliminare correttamente le risorse o non annullare la registrazione dei listener quando non sono più necessari.

Questo documento fornisce alcune best practice per aiutarti a prevenire, rilevare e risolvere le perdite di memoria nel tuo codice. Se hai provato i metodi descritti in questo documento e sospetti una perdita di memoria nei nostri SDK, consulta Come segnalare problemi con gli SDK Google.

Prima di contattare l'assistenza

Prima di segnalare una perdita di memoria al team di assistenza Google, segui le best practice e i passaggi di debug forniti in questo documento per assicurarti che l'errore non sia nel tuo codice. Questi passaggi potrebbero risolvere il problema e, in caso contrario, generano le informazioni di cui il team di assistenza Google ha bisogno per aiutarti.

Prevenire le perdite di memoria

Segui queste best practice per evitare alcune delle cause più comuni di perdite di memoria nel codice che utilizza gli SDK Google.

Best practice per le app Android

Verifica di aver eseguito tutte le seguenti operazioni nella tua app per Android:

  1. Rilascia le risorse inutilizzate.
  2. Annulla la registrazione dei listener quando non sono più necessari.
  3. Annulla le attività quando non sono necessarie.
  4. Inoltra i metodi del ciclo di vita per rilasciare le risorse.
  5. Utilizza le versioni più recenti degli SDK.
  6. Evita di bloccare il thread principale durante l'inizializzazione per evitare gli errori ANR.

Per dettagli specifici su ciascuna di queste pratiche, consulta le sezioni seguenti.

Rilascia le risorse inutilizzate

Quando la tua app per Android utilizza una risorsa, assicurati di rilasciarla quando non è più necessaria. In caso contrario, la risorsa continua a occupare memoria anche dopo che l'applicazione ha terminato di utilizzarla. Per saperne di più, consulta Il ciclo di vita delle attività nella documentazione di Android.

Rilascia i riferimenti GoogleMap obsoleti nei GeoSDK

Un errore comune è che un GoogleMap può causare una perdita di memoria se viene memorizzato nella cache utilizzando NavigationView o MapView. Un GoogleMap ha una relazione 1:1 con NavigationView o MapView da cui viene recuperato. Devi assicurarti che un GoogleMap non venga memorizzato nella cache o che il riferimento venga rilasciato quando viene chiamato NavigationView#onDestroy o MapView#onDestroy. Se utilizzi NavigationSupportFragment, MapSupportFragment o il tuo fragment che racchiude queste visualizzazioni, il riferimento deve essere rilasciato in Fragment#onDestroyView.

class NavFragment : SupportNavigationFragment() {

  var googleMap: GoogleMap?

  override fun onCreateView(
    inflater: LayoutInflater,
    parent: ViewGroup?,
    savedInstanceState: Bundle?,
  ): View  {
    super.onCreateView(inflater,parent,savedInstanceState)
    getMapAsync{map -> googleMap = map}
  }

  override fun onDestroyView() {
    googleMap = null
  }
}

Annulla la registrazione dei listener quando non sono più necessari

Quando la tua app per Android registra un listener per un evento, ad esempio un clic su un pulsante o una modifica dello stato di una visualizzazione, assicurati di annullare la registrazione del listener quando l'applicazione non ha più bisogno di monitorare l'evento. In caso contrario, i listener continuano a occupare memoria anche dopo che l'applicazione ha terminato di utilizzarli.

Ad esempio, supponiamo che la tua applicazione utilizzi l'SDK Navigation e chiami il seguente listener per ascoltare gli eventi di arrivo: addArrivalListener metodo per ascoltare gli eventi di arrivo, deve anche chiamare removeArrivalListener quando non ha più bisogno di monitorare gli eventi di arrivo.

var arrivalListener: Navigator.ArrivalListener? = null

fun registerNavigationListeners() {
  arrivalListener =
    Navigator.ArrivalListener {
      ...
    }
  navigator.addArrivalListener(arrivalListener)
}

override fun onDestroy() {
  navView.onDestroy()
  if (arrivalListener != null) {
    navigator.removeArrivalListener(arrivalListener)
  }

  ...
  super.onDestroy()
}

Annulla le attività quando non sono necessarie

Quando un'app per Android avvia un'attività asincrona, ad esempio un download o una richiesta di rete, assicurati di annullare l'attività al termine. Se l'attività non viene annullata, continua a essere eseguita in background anche dopo che l'app ha terminato di utilizzarla.

Per maggiori dettagli sulle best practice, consulta Gestire la memoria dell'app nella documentazione di Android.

Inoltra i metodi del ciclo di vita per rilasciare le risorse

Se la tua app utilizza l'SDK Navigation o Maps, assicurati di rilasciare le risorse inoltrando i metodi del ciclo di vita (mostrati in grassetto) a navView. Puoi farlo utilizzando NavigationView nell'SDK Navigation o MapView nell'SDK Maps o Navigation. Puoi anche utilizzare SupportNavigationFragment o SupportMapFragment anziché utilizzare direttamente NavigationView e MapView, rispettivamente. I fragment di supporto gestiscono l'inoltro dei metodi del ciclo di vita.

class NavViewActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    navView = ...
    navView.onCreate(savedInstanceState)
    ...
  }

  override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)
    navView.onSaveInstanceState(savedInstanceState)
  }

  override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    navView.onTrimMemory(level)
  }

  /* Same with
    override fun onStart()
    override fun onResume()
    override fun onPause()
    override fun onConfigurationChanged(...)
    override fun onStop()
    override fun onDestroy()
  */
}

Utilizza le versioni più recenti degli SDK

Gli SDK Google vengono costantemente aggiornati con nuove funzionalità, correzioni di bug e miglioramenti delle prestazioni. Mantieni aggiornati gli SDK nella tua app per ricevere queste correzioni.

Evita di bloccare il thread principale durante l'inizializzazione per evitare gli errori ANR

Se un'app blocca il thread principale per troppo tempo, può causare un errore "L'applicazione non risponde" (ANR). Per evitare gli errori ANR, mantieni i metodi del ciclo di vita come onCreate() il più leggeri possibile posticipando le attività a lunga esecuzione o eseguendole al di fuori del thread principale.

Per evitare gli errori ANR correlati all'inizializzazione dell'SDK:

  • Crea una sola istanza della mappa alla volta.
  • Riduci al minimo il lavoro sul thread dell'interfaccia utente il più possibile durante la creazione dell'istanza della mappa.

Eseguire il debug delle perdite di memoria

Se continui a riscontrare perdite di memoria dopo aver implementato tutti i suggerimenti applicabili descritti in precedenza in questo documento, segui questa procedura per eseguire il debug.

Prima di iniziare, devi conoscere il modo in cui Android gestisce la memoria. Per informazioni, consulta la panoramica della gestione della memoria di Android .

Per eseguire il debug delle perdite di memoria:

  1. Riproduci il problema. Questo passaggio è essenziale per il debug.
  2. Controlla se la memoria utilizzata è prevista. Verifica che l'aumento dell'utilizzo che sembra essere una perdita non sia in realtà la memoria necessaria per eseguire l'applicazione.
  3. Esegui il debug a livello generale. Esistono diverse utilità che puoi utilizzare per il debug. Tre diversi set di strumenti standard aiutano a eseguire il debug dei problemi di memoria in Android: Android Studio, Perfetto e le utilità della riga di comando di Android Debug Bridge (adb).
  4. Controlla l'utilizzo della memoria dell'app. Ottieni un dump dell'heap e il monitoraggio dell'allocazione, quindi analizzali.
  5. Correggi le perdite di memoria.

Le sezioni seguenti descrivono questi passaggi in dettaglio.

Passaggio 1: riproduci il problema

Se non sei riuscito a riprodurre il problema, considera prima gli scenari che potrebbero causare la perdita di memoria. Se sai che il problema è stato riprodotto, puoi passare direttamente all'analisi di un dump dell'heap. Tuttavia, se ottieni un dump dell'heap all'avvio dell'app o in un altro momento casuale, potresti non aver attivato le condizioni per attivare una perdita. Quando provi a riprodurre il problema, considera di esaminare vari scenari:

  • Quale insieme di funzionalità è attivato?

  • Quale sequenza specifica di azioni dell'utente attiva la perdita?

    • Hai provato più iterazioni di attivazione di questa sequenza?
  • Quali stati del ciclo di vita ha attraversato l'app?

    • Hai provato più iterazioni in diversi stati del ciclo di vita?

Assicurati di poter riprodurre il problema nell'ultima versione degli SDK. Il problema di una versione precedente potrebbe essere già stato risolto.

Passaggio 2: controlla se la memoria utilizzata per l'app è prevista

Ogni funzionalità richiede memoria aggiuntiva. Quando esegui il debug di scenari diversi, valuta se l'utilizzo potrebbe essere previsto o se si tratta effettivamente di una perdita di memoria. Ad esempio, per diverse funzionalità o attività utente, considera le seguenti possibilità:

  • Probabilmente una perdita:l'attivazione dello scenario tramite più iterazioni comporta un aumento della memoria utilizzata nel tempo.

  • Utilizzo della memoria previsto: la memoria viene recuperata dopo l'interruzione dello scenario.

  • Possibilmente memoria utilizzata prevista: la memoria utilizzata aumenta per un periodo di tempo, poi diminuisce. Ciò potrebbe essere dovuto a una cache limitata o a un'altra memoria utilizzata prevista.

Se il comportamento dell'app è probabilmente l'utilizzo della memoria previsto, il problema può essere risolto gestendo la memoria dell'app. Per assistenza, consulta Gestire la memoria dell'app.

Passaggio 3: esegui il debug a livello generale

Quando esegui il debug di una perdita di memoria, inizia a livello generale, quindi visualizza in dettaglio dopo aver ristretto le possibilità. Utilizza uno di questi strumenti di debug di alto livello per analizzare innanzitutto se si verifica una perdita nel tempo:

Memory Profiler di Android Studio

Questo strumento fornisce un istogramma visivo della memoria utilizzata. Da questa stessa interfaccia è possibile attivare anche i dump dell'heap e il monitoraggio dell'allocazione. Questo strumento è la raccomandazione predefinita. Per saperne di più, consulta Memory Profiler di Android Studio.

Contatori di memoria di Perfetto

Perfetto offre un controllo preciso sul monitoraggio di diverse metriche e le presenta tutte in un unico istogramma. Per saperne di più, consulta Contatori di memoria di Perfetto.

Interfaccia utente di Perfetto

Utilità della riga di comando di Android Debug Bridge (adb)

Gran parte di ciò che puoi monitorare con Perfetto è disponibile anche come utilità della riga di comando adb che puoi interrogare direttamente. Ecco alcuni esempi importanti:

  • Meminfo consente di visualizzare informazioni dettagliate sulla memoria in un determinato momento.

  • Procstats fornisce alcune statistiche aggregate importanti nel tempo.

Una statistica fondamentale da esaminare è il footprint massimo della memoria fisica (maxRSS) richiesto dall'app nel tempo. MaxPSS potrebbe non essere altrettanto preciso. Per aumentare la precisione, consulta il flag adb shell dumpsys procstats --help –start-testing.

Monitoraggio dell'allocazione

Il monitoraggio dell'allocazione identifica l'analisi dello stack in cui è stata allocata la memoria e se non è stata liberata. Questo passaggio è particolarmente utile per individuare le perdite nel codice nativo. Poiché questo strumento identifica l'analisi dello stack, può essere un ottimo modo per eseguire rapidamente il debug della causa principale o per capire come riprodurre il problema. Per i passaggi per utilizzare il monitoraggio dell'allocazione, consulta Eseguire il debug della memoria nel codice nativo con il monitoraggio dell'allocazione.

Passaggio 4: controlla l'utilizzo della memoria dell'app con un dump dell'heap

Un modo per rilevare una perdita di memoria è ottenere un dump dell'heap dell'app e quindi ispezionarlo per verificare la presenza di perdite. Un dump dell'heap è uno snapshot di tutti gli oggetti nella memoria di un'app. Può essere utilizzato per diagnosticare perdite di memoria e altri problemi correlati alla memoria.

Android Studio può rilevare perdite di memoria non correggibili dal GC. Quando acquisisci un dump dell'heap, Android Studio verifica se esiste un'attività o un fragment ancora raggiungibile ma già eliminato.

  1. Acquisisci un dump dell'heap.
  2. Analizza il dump dell'heap per trovare le perdite di memoria.
  3. Correggi le perdite di memoria.

Per maggiori dettagli, consulta le sezioni seguenti.

Acquisisci un dump dell'heap

Per acquisire un dump dell'heap, puoi utilizzare Android Debug Bridge (adb) o Memory Profiler di Android Studio.

Utilizza adb per acquisire un dump dell'heap

Per acquisire un dump dell'heap utilizzando adb:

  1. Collega il dispositivo Android al computer.
  2. Apri un prompt dei comandi e vai alla directory in cui si trovano gli strumenti adb.
  3. Per acquisire un dump dell'heap, esegui questo comando :

    adb shell am dumpheap my.app.name $PHONE_FILE_OUT

  4. Per recuperare il dump dell'heap, esegui questo comando:

    adb pull $PHONE_FILE_OUT $LOCAL_FILE.

Utilizza Android Studio per acquisire un dump dell'heap

Per acquisire un dump dell'heap utilizzando Memory Profiler di Android Studio, segui questi passaggi nella sezione Android Acquisire un dump dell'heap.

Analizza il dump dell'heap per trovare le perdite di memoria

Dopo aver acquisito un dump dell'heap, puoi utilizzare Memory Profiler di Android Studio per analizzarlo. A questo scopo:

  1. Apri il progetto Android in Android Studio.

  2. Seleziona Esegui, quindi seleziona la configurazione Debug.

  3. Apri la scheda Android Profiler.

  4. Seleziona Memoria.

  5. Seleziona Apri dump dell'heap e seleziona il file di dump dell'heap che hai generato. Memory Profiler mostra un grafico della memoria utilizzata dall'app.

  6. Utilizza il grafico per analizzare il dump dell'heap:

    • Identifica gli oggetti non più utilizzati.

    • Identifica gli oggetti che utilizzano molta memoria.

    • Scopri quanta memoria utilizza ogni oggetto.

  7. Utilizza queste informazioni per restringere o trovare l'origine della perdita di memoria e correggerla.

Passaggio 5: correggi le perdite di memoria

Dopo aver identificato l'origine della perdita di memoria, puoi correggerla. La correzione delle perdite di memoria nelle app Android contribuisce a migliorare le prestazioni e la stabilità delle app. A seconda dello scenario, i dettagli variano. Tuttavia, i seguenti suggerimenti possono essere utili:

Altri strumenti di debug

Se, dopo aver completato questi passaggi, non hai ancora trovato e corretto la perdita di memoria, prova questi strumenti:

Esegui il debug della memoria nel codice nativo con il monitoraggio dell'allocazione

Anche se non utilizzi direttamente il codice nativo, diverse librerie Android comuni lo fanno, inclusi gli SDK Google. Se ritieni che la perdita di memoria sia nel codice nativo, puoi utilizzare diversi strumenti per eseguirne il debug. Il monitoraggio dell'allocazione con Android Studio o heapprofd (compatibile anche con Perfetto) è un ottimo modo per identificare le potenziali cause di una perdita di memoria ed è spesso il modo più rapido per eseguire il debug.

Il monitoraggio dell'allocazione presenta anche il vantaggio di consentirti di condividere i risultati senza includere informazioni sensibili che possono essere trovate in un heap.

Identifica le perdite con LeakCanary

LeakCanary è uno strumento potente per identificare le perdite di memoria nelle app Android. Per saperne di più su come utilizzare LeakCanary nella tua app, visita LeakCanary.

Come segnalare problemi con gli SDK Google

Se hai provato i metodi descritti in questo documento e sospetti una perdita di memoria nei nostri SDK, contatta l'assistenza clienti fornendo quante più informazioni possibili tra le seguenti:

  • Passaggi per riprodurre la perdita di memoria. Se i passaggi richiedono una codifica complessa, può essere utile copiare il codice che riproduce il problema nella nostra app di esempio e fornire passaggi aggiuntivi da eseguire nell'UI per attivare la perdita.

  • Dump dell'heap acquisiti dalla tua app con il problema riprodotto. Acquisisci i dump dell'heap in due momenti diversi che mostrano che la memoria utilizzata è aumentata in modo sostanziale.

  • Se è prevista una perdita di memoria nativa, condividi l'output del monitoraggio dell'allocazione da heapprofd.

  • Una segnalazione di bug acquisita dopo aver riprodotto la condizione di perdita.

  • Analisi dello stack di eventuali arresti anomali correlati alla memoria.

    Nota importante: in genere, le analisi dello stack non sono sufficienti da sole per eseguire il debug di un problema di memoria, quindi assicurati di fornire anche una delle altre forme di informazioni.