LeakCanary è una libreria open source di Square per il rilevamento automatico di perdite di memoria nelle applicazioni Android. Si integra nel processo di sviluppo e monitora in tempo reale il ciclo di vita di Activity, Fragment, ViewModel e altri componenti, segnalando le perdite non appena si verificano. Secondo Square Open Source, la libreria è utilizzata in migliaia di progetti ed è considerata lo standard de facto per la diagnostica della memoria su Android.
Punti chiave
LeakCanary è una libreria per il rilevamento automatico di perdite di memoria nelle applicazioni Android, sviluppata da Square. Si integra nel processo di build dell'app e monitora automaticamente se gli oggetti che dovrebbero essere distrutti (Activity, Fragment, View) rimangono in memoria. Quando viene rilevata una perdita, LeakCanary genera un heap dump e analizza la catena di riferimenti che trattiene l'oggetto.
La libreria è diventata uno standard nella comunità Android: secondo GitHub, il progetto ha oltre 28 mila stelle ed è utilizzato nelle applicazioni di Google, Uber, Airbnb e Facebook. LeakCanary è disponibile in due versioni principali: la classica 1.x (con configurazione manuale) e la moderna 2.x (integrazione automatica tramite ContentProvider). La versione 2.x non richiede la modifica della classe Application — la dipendenza è sufficiente per il funzionamento completo.
Il compito principale di LeakCanary è rilevare quando un oggetto continua a esistere in memoria dopo la fine del suo ciclo di vita. Questo è tipico delle perdite attraverso campi statici, singleton, callback non deregistrati, classi anonime e closure che catturano oggetti esterni.
Le perdite di memoria su Android sono più critiche che sul desktop a causa della RAM limitata dei dispositivi mobili. Anche una perdita di 5–10 MB a ogni transizione di schermata può portare a un OutOfMemoryError dopo 30–40 minuti di utilizzo dell'app. LeakCanary rileva questi problemi nella fase di sviluppo, senza aspettare un crash in produzione.
LeakCanary utilizza riferimenti deboli (WeakReference) combinati con garbage collection forzata. Quando un'Activity o un Fragment chiama onDestroy, LeakCanary crea un WeakReference su quell'oggetto e attiva il GC dopo un breve ritardo (5 secondi per impostazione predefinita). Se l'oggetto è ancora accessibile tramite WeakReference dopo il GC, viene trattenuto da un riferimento forte — viene registrata una perdita.
Dopo aver rilevato una perdita, LeakCanary esegue un heap dump (dump della memoria) — un'istantanea completa della memoria dell'app in formato HPROF. Quindi l'analizzatore integrato (Shark per la versione 2.x) costruisce un grafo di raggiungibilità dalle GC Roots all'oggetto perso e trova il percorso più breve — la catena di riferimenti che mantiene l'oggetto in memoria.
// Logica semplificata di rilevamento di LeakCanary
class ObjectWatcher {
private val watchedReferences = CopyOnWriteArrayList<KeyedWeakReference>()
fun watch(watchedObject: Any, description: String) {
val reference = KeyedWeakReference(watchedObject, description)
watchedReferences.add(reference)
BackgroundHandler.postDelayed({
checkForLeaks()
}, 5000)
}
private fun checkForLeaks() {
GcTrigger.runGc() // GC forzato
for (ref in watchedReferences) {
if (ref.get() != null) {
onLeakFound(ref) // l'oggetto è sopravvissuto al GC — è una perdita
}
}
}
}
Il punto chiave è la chiamata forzata a GcTrigger.runGc(). Senza di essa, è impossibile distinguere un oggetto che è effettivamente perso da uno che il GC non ha ancora raccolto. LeakCanary lo fa fino a tre volte: se dopo tre cicli di GC l'oggetto è ancora in memoria, la perdita è confermata.
Shark è l'analizzatore di heap dump integrato in LeakCanary 2.x, scritto in Kotlin. A differenza del precedente analizzatore HAHA, Shark non carica l'intero file HPROF in memoria, ma attraversa il suo grafo di oggetti con allocazioni minime. Ciò riduce il consumo di RAM durante l'analisi da 50 MB a 2–5 MB e accorcia il tempo di analisi da 30 secondi a 1–3 secondi.
Installare LeakCanary 2.x in un progetto Android moderno richiede una riga in build.gradle. La libreria utilizza ContentProvider per l'inizializzazione automatica — non è necessario modificare la classe Application né aggiungere codice a MainActivity. La dipendenza viene aggiunta solo per le build debug, in modo che gli APK di rilascio non contengano codice extra.
// build.gradle (app/module)
dependencies {
// debugImplementation — libreria solo per build debug
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
}
Dopo aver aggiunto la dipendenza e ricostruito il progetto, LeakCanary appare automaticamente nell'app. Al primo avvio, la libreria mostra una notifica di sistema che conferma l'attivazione. Tutte le perdite rilevate appaiono come notifiche — toccando una notifica si apre una schermata con un report dettagliato (LeakTrace).
Per la personalizzazione, puoi creare il tuo AppWatcherInstaller e sovrascrivere i parametri: timeout GC, elenco dei tipi di oggetti tracciati, abilitazione del salvataggio dell'heap dump su disco. Tuttavia, per il 90% dei progetti, la configurazione predefinita è ottimale.
A partire dalla versione 2.12, LeakCanary supporta il tracciamento automatico di ViewModel, ambiti delle coroutine e oggetti State di Compose. Non sono necessarie dipendenze aggiuntive — la libreria rileva automaticamente quali componenti Jetpack sono utilizzati nel progetto e attiva i relativi rilevatori.
Un report di LeakCanary (LeakTrace) è una catena di riferimenti a più righe dalla GC Root all'oggetto perso. Ogni riga mostra la classe e il campo attraverso cui passa un riferimento forte. Lo sviluppatore deve leggere la catena dal basso verso l'alto: la riga inferiore è l'oggetto perso, la riga superiore è il punto di ingresso (GC Root).
Un LeakTrace tipico appare così: GC Root → campo statico di Application → singleton → callback → Activity. Se uno sviluppatore vede questa catena, il problema è chiaro: il singleton trattiene un callback che ha catturato un riferimento all'Activity. La soluzione è sostituire il riferimento forte con uno debole nel singleton.
┬
├─ android.app.Application
│ Leaking: NO (Application — singleton)
│ ↓ Application.leakedActivities
├─ java.util.ArrayList
│ Leaking: NO (ArrayList — normal)
│ ↓ ArrayList[0]
├─ com.example.MainActivity
│ Leaking: YES (Activity destroyed but still in memory)
│ ↓ MainActivity.mCallback
├─ com.example.CallbackWrapper
│ Leaking: UNKNOWN
│ ↓ CallbackWrapper.mListener
│ ~~~~~~~~~~
├─ com.example.MyCallback (anonymous)
│ Leaking: UNKNOWN
│ ↓ MyCallback.this$0
├─ com.example.MainActivity
│ Leaking: YES (MainActivity is the leak)
╰
In questo esempio, LeakCanary mostra che MainActivity è trattenuta attraverso la catena: Application → ArrayList → MainActivity → CallbackWrapper → MyCallback → di nuovo MainActivity. La freccia this$0 indica che la classe anonima MyCallback ha catturato un riferimento esterno all'Activity. La soluzione è rendere il callback un riferimento debole o cancellarlo in onDestroy.
LeakCanary mostra anche lo stato della perdita per ogni elemento della catena: NO (nessuna perdita — elemento radice), YES (l'oggetto dovrebbe essere distrutto), UNKNOWN (stato indeterminato). Lo stato UNKNOWN non significa un problema — è un oggetto intermedio che LeakCanary non può classificare in modo definitivo.
Il passaggio dalla versione 1.x alla 2.x è stato radicale: gli sviluppatori hanno riscritto la libreria da zero, sostituendo il vecchio analizzatore HAHA con il proprio motore Shark, scritto in Kotlin. Shark è un ordine di grandezza più veloce, richiede meno memoria per l'analisi e determina con maggiore precisione le cause profonde delle perdite.
| Parametro | LeakCanary 1.x | LeakCanary 2.x |
|---|---|---|
| Linguaggio analizzatore | Java (HAHA — fork dell'Android SDK) | Kotlin (Shark — motore proprietario) |
| Installazione | Configurazione manuale di AppWatcher in Application | Automatica tramite ContentProvider |
| Velocità | 10–30 secondi per l'analisi dell'heap dump | 1–5 secondi per l'analisi dell'heap dump |
| Prestazioni | Consuma 10–50 MB di RAM durante l'analisi | Consuma 2–10 MB di RAM durante l'analisi |
Il vantaggio chiave di Shark è che non carica l'intero heap dump in memoria, ma attraversa il suo grafo di riferimenti con allocazioni minime. Questo rende LeakCanary 2.x utilizzabile su dispositivi con poca RAM senza il rischio di OutOfMemoryError durante l'analisi.
La versione 2.x ha anche introdotto la possibilità di esportare gli heap dump in un file per l'analisi successiva in Android Studio Memory Profiler. Per farlo, attiva l'impostazione dumpHeapWhenLeakFound nella configurazione di AppWatcher.
LeakCanary rileva efficacemente diverse classi di perdite comuni in Android. La più frequente è la perdita attraverso riferimenti statici a un'Activity — gli sviluppatori mantengono un riferimento al contesto dell'Activity in un singleton, e l'Activity non può essere raccolta dal GC dopo la fine del suo ciclo di vita.
La seconda categoria più comune sono le perdite attraverso listener non deregistrati. Se registerListener è stato chiamato in onStart ma unregisterListener non è stato chiamato in onStop/onDestroy, l'oggetto listener viene trattenuto dal sistema anche dopo la distruzione dell'attività. LeakCanary mostra chiaramente quale listener e in quale servizio di sistema rimane vivo.
// Perdita tipica: Activity catturata in un callback di singleton
object AnalyticsManager {
private var callback: ((String) -> Unit)? = null
fun register(callback: (String) -> Unit) {
this.callback = callback // riferimento forte al callback
}
fun unregister() {
callback = null // NON DIMENTICARE di chiamare in onDestroy!
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
AnalyticsManager.register { event ->
logEvent(event) // lambda cattura this
}
// se unregister non viene chiamato in onDestroy → perdita di Activity
}
}
La terza categoria sono le perdite attraverso Fragment nel BackStack. Se FragmentTransaction.addToBackStack() viene chiamato senza rimuovere il Fragment durante il ritorno, le vecchie istanze di Fragment rimangono in memoria. LeakCanary aiuta a rilevare queste perdite nascoste nelle prime fasi dello sviluppo.
Per ogni perdita rilevata, LeakCanary fornisce una descrizione e raccomandazioni per la correzione. La versione 2.14 ha aggiunto l'integrazione con Android Lint — la libreria può creare automaticamente attività nel tracker dei problemi quando viene rilevata una perdita in CI.
Domande frequenti
Sì, assolutamente. LeakCanary viene aggiunto tramite debugImplementation in build.gradle, che lo esclude automaticamente dalle build di rilascio. Se si utilizza implementation, la libreria verrà inclusa nell'APK di rilascio e mostrerà le perdite agli utenti finali — questo è inaccettabile.
L'impatto sulle prestazioni è minimo. LeakCanary si attiva solo dopo l'onDestroy del componente e non interferisce con il rendering dell'interfaccia o la gestione dei tocchi. L'unico costo è una breve pausa forzata del GC (circa 100 ms) e la scrittura dell'heap dump quando si verifica una perdita (frazioni di secondo).
LeakCanary salva automaticamente gli heap dump in formato HPROF nella cartella dell'app. Il file può essere esportato tramite Android Studio: Device File Explorer → data/data/com.example/files/leakcanary/. Per visualizzarlo, apri il file in Memory Profiler tramite Capture → Open Heap Dump.
Sì, a partire dalla versione 2.12 LeakCanary supporta completamente Jetpack Compose. La libreria traccia i contesti di Composition e gli oggetti State, rilevando automaticamente le perdite nelle funzioni Composable. Non è necessaria una configurazione separata — funziona immediatamente.
I falsi positivi sono possibili ma rari. LeakCanary utilizza una triplice chiamata al GC prima di dichiarare una perdita, eliminando la maggior parte dei falsi positivi. Se ritieni che un rilevamento sia un falso positivo, crea un IgnoredReference per la classe specifica nella configurazione.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche