Best practice di gestione della memoria

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

Introduzione

Una fuga di memoria è un tipo di perdita di risorse che si verifica quando un programma informatico non rilascia la memoria allocata che non è più necessaria. Una perdita può portare l'applicazione a richiedere al sistema operativo più memoria di quella che ha a disposizione, causando così l'arresto anomalo dell'applicazione. Varie pratiche improprie possono causare perdite di memoria nelle app per Android, ad esempio uno smaltimento non corretto delle risorse o il mancato annullamento della registrazione dei listener quando non sono più necessari.

Questo documento fornisce alcune best practice per aiutare a prevenire, rilevare e risolvere le perdite di memoria nel 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 dell'Assistenza Google, segui le best practice e i passaggi di debug forniti in questo documento per assicurarti che l'errore non sia presente nel tuo codice. Questi passaggi potrebbero risolvere il problema e, in caso contrario, generano le informazioni necessarie al team dell'Assistenza Google.

Prevenzione delle fughe di memoria

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

Best practice per le app per Android

Controlla di aver eseguito tutte le seguenti operazioni nell'applicazione Android:

  1. Rilascia le risorse non utilizzate.
  2. Annulla la registrazione dei listener quando non sono più necessari.
  3. Annullare le attività quando non sono necessarie.
  4. Inoltrare i metodi del ciclo di vita per rilasciare risorse.
  5. Utilizzare le versioni più recenti degli SDK

Per informazioni dettagliate su ciascuna di queste pratiche, consulta le sezioni seguenti.

Rilascia risorse inutilizzate

Quando la tua app per Android utilizza una risorsa, assicurati di rilasciarla quando non è più necessaria. In caso contrario, la risorsa continuerà a occupare memoria anche dopo che l'applicazione è stata utilizzata. Per ulteriori informazioni, consulta Il ciclo di vita dell'attività nella documentazione di Android.

Rilascia riferimenti GoogleMap obsoleti nei GeoSDK

Un errore comune è che una mappa GoogleMap possa causare una perdita di memoria se memorizzata nella cache utilizzando NavigatorView o MapView. Una mappa di GoogleMap ha una relazione di 1 a 1 con NavigationView o MapView da cui viene recuperata. Devi assicurarti che la mappa GoogleMap non sia memorizzata nella cache o che il riferimento venga rilasciato quando viene chiamato NavigationView#onDestroy o MapView#onDestroy. Se utilizzi NavigationSupportFragment, MapSupportFragment o un tuo frammento che esegue un wrapping di queste viste, 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 deve più monitorare l'evento. In caso contrario, i listener continueranno a occupare memoria anche dopo che l'applicazione è stata completata.

Ad esempio, supponi che la tua applicazione utilizzi l'SDK di navigazione e chiami il seguente listener per ascoltare gli eventi di arrivo: il metodo addArrivalListener per ascoltare gli eventi di arrivo, dovrebbe anche chiamare removeArrivalListener quando non deve più 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 è stata completata.

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

Inoltrare metodi del ciclo di vita per rilasciare risorse

Se la tua app utilizza l'SDK Navigation o Maps, assicurati di rilasciare le risorse inviando i metodi del ciclo di vita (mostrati in grassetto) a navView. Puoi a questo scopo utilizzando NavigationView nell'SDK Navigazione o MapView nell'SDK Maps o Navigazione. Puoi anche utilizzare SupportNavigationFragment o SupportMapFragment anziché utilizzare direttamente NavigationView e MapView rispettivamente. I frammenti 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.

Esegui il debug delle fughe di memoria

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

Prima di iniziare, dovresti avere familiarità con come Android gestisce la memoria. Per informazioni, leggi la Panoramica sulla gestione della memoria di Android.

Per eseguire il debug delle perdite di memoria, segui questa procedura:

  1. Ricrea il problema. Questo passaggio è essenziale per il debug.
  2. Controlla se è previsto l'utilizzo della memoria. Controlla che l'aumento dell'utilizzo che sembra essere una perdita non sia effettivamente la memoria necessaria per eseguire l'applicazione.
  3. Esegui il debug a livello generale. Ci sono diverse utilità che puoi usare per il debug. Esistono tre diversi set di strumenti standard che consentono di eseguire il debug dei problemi di memoria in Android: Android Studio, Perfetto e le utilità a riga di comando di Android Debug Bridge (adb).
  4. Controlla la quantità di memoria utilizzata dall'app. Ottieni un dump dell'heap e il monitoraggio dell'allocazione.
  5. Correggi le perdite di memoria.

Le sezioni seguenti illustrano in dettaglio questi passaggi.

Passaggio 1: ricrea il problema

Se non sei riuscito a ricreare il problema, considera innanzitutto gli scenari che potrebbero causare una perdita di memoria. Se sai già che il problema è stato ricreato, potrebbe funzionare anche un dump dell'heap dump. Tuttavia, se ottieni un dump dell'heap all'avvio dell'app o un altro momento casuale, potresti non aver attivato le condizioni per attivare una perdita. Prova a ricreare il problema in diversi scenari:

  • Quale insieme di funzionalità viene attivato?

  • Quale sequenza specifica di azioni degli utenti ha scatenato la fuga di notizie?

    • Hai provato diverse iterazioni per attivare questa sequenza?
  • Quali stati del ciclo di vita ha proseguito l'app?

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

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

Passaggio 2: controlla se è previsto l'utilizzo della memoria per l'app

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

  • Probabile perdita: l'attivazione dello scenario attraverso più iterazioni comporta un aumento dell'utilizzo della memoria nel tempo.

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

  • Possibile utilizzo della memoria previsto: l'utilizzo della memoria aumenta per un determinato periodo di tempo, quindi diminuisce. Ciò potrebbe essere dovuto a una cache limitata o a un altro utilizzo previsto della memoria.

Se il comportamento dell'app è probabile che utilizzi la memoria, puoi risolvere il problema gestendo la memoria dell'app. Per informazioni, consulta l'articolo Gestire la memoria dell'app.

Passaggio 3: esegui il debug ad alto livello

Quando esegui il debug di una perdita di memoria, inizia dal livello più alto per poi andare in dettaglio una volta che hai ristretto le possibilità. Utilizza uno di questi strumenti di debug di alto livello per analizzare innanzitutto eventuali perdite nel tempo:

Profiler di memoria di Android Studio

Questo strumento fornisce un istogramma visivo della memoria utilizzata. Anche i dump dell'heap e il monitoraggio dell'allocazione possono essere attivati da questa stessa interfaccia. Questo strumento è il suggerimento predefinito. Per ulteriori informazioni, consulta la pagina relativa a Android Studio Memory Profiler.

Contatori di memoria Perfetto

Perfetto ti permette di controllare in modo preciso il monitoraggio di diverse metriche e le presenta tutte in un unico istogramma. Per ulteriori informazioni, vedi Contatori di memoria perfetti.

Interfaccia utente di Perfetto

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

Molto degli elementi che puoi monitorare con Perfetto è disponibile anche come utilità a riga di comando adb su cui puoi eseguire query direttamente. Due esempi importanti sono:

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

  • Procstats fornisce alcune importanti statistiche aggregate nel tempo.

Una statistica fondamentale da esaminare è l'impronta di memoria fisica massima (maxRSS) richiesta dall'app nel tempo. Il valore MaxPSS potrebbe non essere così preciso. Per un modo per aumentare l'accuratezza, 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 tenere traccia delle fughe di codice nativo. Poiché questo strumento identifica l'analisi dello stack, può essere un ottimo strumento per eseguire rapidamente il debug della causa principale o per capire come ricreare il problema. Per la procedura di utilizzo del monitoraggio delle allocazioni, consulta Debug della memoria in codice nativo con monitoraggio delle allocazioni.

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

Un modo per rilevare una perdita di memoria è scaricare un heap dump della tua app e quindi ispezionarla per individuare eventuali perdite. Un dump dell'heap è uno snapshot di tutti gli oggetti nella memoria di un'app. Può essere usato per diagnosticare le perdite di memoria e altri problemi legati alla memoria.

Android Studio è in grado di rilevare perdite di memoria non risolvibili dal GC. Quando acquisisci un dump dell'heap, Android Studio controlla se è presente un'attività o un frammento ancora raggiungibile, ma che è già stato eliminato.

  1. Acquisisci un dump dell'heap.
  2. Analizza il dump dell'heap per trovare perdite di memoria.
  3. Risolvi 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 Android Studio Memory Profiler.

Utilizzare adb per acquisire un dump dell'heap

Per acquisire un dump dell'heap utilizzando adb, segui questi passaggi:

  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.

Utilizzare Android Studio per acquisire un dump dell'heap

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

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

Dopo aver acquisito un dump dell'heap, puoi usare Android Studio Memory Profiler per analizzarlo. Per farlo, segui questi passaggi:

  1. Apri il tuo progetto Android in Android Studio.

  2. Seleziona Esegui e poi la configurazione Debug.

  3. Apri la scheda Profilor Android.

  4. Seleziona Memoria.

  5. Seleziona Apri dump heap e seleziona il file di dump dell'heap che hai generato. Il profiler della memoria mostra un grafico della memoria utilizzata dalla tua app.

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

    • Identificare gli oggetti che non vengono più utilizzati.

    • Identificare gli oggetti che utilizzano molta memoria.

    • Verifica la quantità di memoria utilizzata da ciascun oggetto.

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

Passaggio 5: correggi le perdite di memoria

Una volta identificata l'origine della perdita di memoria, puoi correggerla. La correzione delle perdite di memoria nelle app Android consente di migliorare le prestazioni e la stabilità delle app. I dettagli variano in base allo scenario. Tuttavia, i seguenti suggerimenti possono essere utili:

Altri strumenti di debug

Se una volta completati questi passaggi non hai ancora trovato e corretto la perdita di memoria, prova questi strumenti:

Memoria di debug in codice nativo con monitoraggio delle allocazioni

Anche se non utilizzi direttamente il codice nativo, esistono diverse librerie Android comuni, tra cui gli SDK di Google. Se pensi che la perdita di memoria sia nel codice nativo, puoi usare 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 distintivo di condividere i risultati senza includere informazioni sensibili che possono essere trovate in un heap.

Identifica le fughe di notizie con LeakCanary

LeakCanary è un potente strumento per identificare le fughe di memoria nelle app per Android. Per scoprire 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ù delle seguenti informazioni possibile:

  • Passaggi per ricreare la perdita di memoria. Se i passaggi richiedono una codifica complessa, potrebbe essere utile copiare il codice che replica il problema nella nostra app di esempio e fornire passaggi aggiuntivi da eseguire nella UI per attivare la fuga di dati.

  • Dump dell'heap acquisiti dalla tua app con il problema ricreato. Acquisisci dump dell'heap in due diversi punti nel tempo che mostrano che l'utilizzo della memoria è aumentato di molto.

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

  • Una segnalazione di bug generata dopo aver ricreato la condizione di perdita.

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

    Nota importante: in genere le analisi dello stack non sono sufficienti per eseguire il debug di un problema di memoria, quindi assicurati di fornire anche uno degli altri tipi di informazioni.