Android memory leak

Come una corretta gestione del ciclo di vita può prevenire le perdite di memoria di Android

OutOfMemoryException è un bug comune e frustrante, e una delle prime cause di chiusura inaspettata delle app.

“Perché sta succedendo ora, se ieri l’app funzionava perfettamente?” È una domanda che lascia perplessi sia i principianti che gli sviluppatori Android più esperti di tutto il mondo.

Ci sono una varietà di potenziali cause delle OutOfMemory Exceptions, ma una delle più comuni è il memory leak – l’allocazione di memoria nell’app che non viene mai rilasciata. Questo articolo spiegherà come minimizzare questo rischio attraverso una gestione efficace del ciclo di vita – una parte cruciale ma spesso trascurata del processo di sviluppo.

TL;DR

In questo articolo discuteremo:

  • Le ragioni del memory leakage di Android
  • Passi base per prevenirlo
  • Forme più avanzate di gestione del ciclo di vita
  • Strumenti che possono gestire il ciclo di vita del tuo prodotto

Perché il memory leakage avviene su Android?

Il problema è semplice. Alcuni oggetti dovrebbero avere una durata di vita fissa, e quando la loro vita utile è finita, devono essere rimossi.

In teoria, questa memoria dovrebbe essere eliminata quando un processo viene ucciso usando onStop o onDestroy. Tuttavia, un uso improprio della referenziazione degli oggetti può impedire al garbage collector di deallocare gli oggetti inutilizzati. Per esempio: se l’oggetto inutilizzato A fa riferimento all’oggetto inutilizzato B, vi ritroverete con due oggetti inutili che il garbage collector non deallocherà mai, dato che fanno riferimento l’uno all’altro.

Trucchi comuni per impedire che questo accada

Gli sviluppatori possono prendere una serie di misure per impedire che le attività morte siano intrappolate nella memoria.

  1. Registra/disregistra i BroadcastReceiver in onResume()/onPause() o onStart()/onStop()
  2. Non usare MAI variabili statiche per Views/Activities/Contexts
  3. I singleton che hanno bisogno di tenere riferimenti al Context dovrebbero usare applicationContext() o avvolgerlo in WeakReference
  4. Stare attenti alle classi interne anonime e nonstatiche, in quanto tengono un riferimento implicito alla classe che le racchiude.
    1. Utilizzare classi interne statiche invece che anonime se stanno per sopravvivere alla classe padre (come i gestori).
    2. Se la classe interna o anonima è cancellabile (come AsyncTask, Thread, RxSubscriptions), cancellarla quando l’attività viene distrutta.

I componenti Android Lifecycle Aware

Una volta completati i passi base di cui sopra, è il momento di qualcosa di molto più importante: il ciclo di vita delle attività dell’app. Se non gestiamo correttamente il ciclo di vita, finiremo per rimanere aggrappati alla memoria quando non è più necessaria.

Ci sono molti compiti diversi coinvolti in questo. Per ogni attività, dobbiamo interrompere i thread, sbarazzarci delle sottoscrizioni in RxJava, cancellare i riferimenti AsyncTask e assicurarci che il riferimento a questa attività (e a qualsiasi altra attività collegata ad essa) sia correttamente rimosso. Tutti questi compiti possono drenare un’enorme quantità di tempo dello sviluppatore.

Le cose sono rese ancora più complicate dal Model View Presenter (MVP), il pattern architetturale spesso impiegato per costruire l’interfaccia utente in Android. MVP, tuttavia, è utile per disaccoppiare la logica di business dalla View.

Nel pattern MVP, sia View che Presenter sono implementazioni astratte di un contratto per un comportamento tra loro. Il modo più comune per implementare MVP è usare un’attività/frammento come implementazione per la vista e una semplice implementazione per il presentatore che è abituato ad avere un riferimento alla vista.

Così finiamo per avere una vista con un riferimento al presentatore e un presentatore con un riferimento alla vista (Suggerimento: abbiamo una potenziale perdita qui).

Viste queste potenziali difficoltà, è essenziale mettere in atto una struttura di gestione adeguata per rimuovere la memoria in eccesso creata durante il ciclo di vita. Ci sono diversi modi collaudati per farlo:

Creazione di componenti consapevoli del ciclo di vita utilizzando Android Arch Lifecycle su Android Studio

I componenti consapevoli del ciclo di vita sono intelligenti. Possono reagire ad un cambiamento nello stato del ciclo di vita di un altro componente, come attività o frammenti, liberandosi della memoria, per esempio. Questo significa che il codice è più leggero e molto più efficiente in termini di memoria.

Arch Lifecycle è una nuova libreria di Android che offre una serie di strumenti per costruire componenti lifecycle-aware. La libreria funziona in modo astratto, il che significa che i proprietari del ciclo di vita non devono più preoccuparsi di gestire il ciclo di vita di specifici compiti e attività.

Gli strumenti chiave e le definizioni di Arch Lifecycle sono i seguenti:

  • LifeCycle: Un sistema di ordinamento che definisce quali oggetti hanno un ciclo di vita Android, e permette loro di essere poi monitorati.
  • LifecycleObserver: Un’interfaccia regolare che monitora ogni oggetto identificato come avente un ciclo di vita Android, utilizzando una semplice formula per gestire ogni evento chiave del ciclo di vita.
  • @OnLifecycleEvent: Un’annotazione che può essere usata nelle classi che implementano l’interfaccia LifecycleObserver. Ci permette di impostare gli eventi chiave del ciclo di vita che faranno scattare il metodo annotato ogni volta che viene lanciato. Ecco una lista di tutti gli eventi che possono essere impostati:
    • ON_ANY
    • ON_CREATE
    • ON_DESTROY
    • ON_PAUSE
    • ON_RESUME
    • ON_START
    • ON_STOP
  • LifecycleOwner è implementato di default per ogni componente Android il cui ciclo di vita può essere gestito, e dà allo sviluppatore il controllo di ogni evento.

Utilizzando questi strumenti, siamo in grado di inviare tutti i compiti puliti ai loro proprietari (presentatori nel nostro caso), quindi abbiamo un codice pulito, disaccoppiato e privo di perdite (almeno nel livello del presentatore).

Ecco un’implementazione super-basica per mostrarvi di cosa stiamo parlando:

interface View: MVPView, LifecycleOwnerclass RandomPresenter : Presenter<View>, LifecycleObserver { private lateinit var view: View override fun attachView(view: View) { this.view = view view.lifecycle.addObserver(this) } @OnLifecycleEvent(Lifecycle.Event.On_DESTROY) fun onClear() {//TODO: clean }

Using Android Arch View Models as Presenters and LiveData

Un altro modo per evitare perdite di memoria attraverso la cattiva gestione del ciclo di vita è usare i nuovi ViewModels della Arch Components Library.

Un ViewModel è una classe astratta che implementa una singola funzione nota come onClear, che viene chiamata automaticamente quando un particolare oggetto deve essere rimosso. Il ViewModel è generato dal framework ed è attaccato al ciclo di vita del creatore (come bonus aggiuntivo, è super-facile da iniettare con Dagger.)

Oltre a usare ViewModel, LiveData fornisce anche un canale vitale di comunicazione. Segue un pattern reattivo e facile da osservare, il che significa che i singoli oggetti possono osservarlo (creando un codice meno accoppiato).

Il grande punto qui è che LiveData può essere osservato da un proprietario del ciclo di vita, quindi il trasferimento dei dati è sempre gestito dal ciclo di vita -e possiamo assicurarci che ogni riferimento venga mantenuto mentre li usiamo.

Usare LeakCanary e Bugfender

Oltre ai passi sopra menzionati, vogliamo raccomandare due importanti strumenti: LeakCanary, uno strumento popolare per monitorare le perdite, e il nostro Bugfender.

LeakCanary è una libreria di rilevamento della memoria per Android e Java. È open-source, quindi c’è un’enorme comunità dietro di essa, e non si limita a dirvi di una perdita – vi informa della probabile causa.

Bugfender, il nostro strumento di registrazione remota, consente di eseguire il debug di singole LeakTraces ed estendere una classe chiamata DisplayLeakService, che ci permette di sapere quando viene sollevata una perdita. Poi possiamo registrarla facilmente con Bugfender.

public class LeakUploadService extends DisplayLeakService { override fun afterDefaultHandling(heapDump: HeapDump, result: AnalysisResult, leakInfo: String) { if (result.leakFound) { Bugfender.d("LeakCanary", result.toString()) } }}

Inoltre, gli utenti ottengono tutti gli altri vantaggi di Bugfender, compresa la registrazione dei log 24/7 (anche quando il dispositivo è offline), la segnalazione di crash integrata e una console web facile da usare, che permette il drill-down nei singoli dispositivi per una migliore assistenza clienti.

Per saperne di più su Bugfender, clicca qui.

Leave a Reply