LeakCanary — este o bibliotecă open source de la Square pentru detectarea automată a scurgerilor de memorie în aplicațiile Android. Se integrează în procesul de dezvoltare și în timp real urmărește ciclul de viață al Activity, Fragment, ViewModel și altor componente, semnalând scurgerile imediat după apariția lor. Conform datelor Square Open Source, biblioteca este utilizată în mii de proiecte și este considerată standardul de facto pentru diagnosticarea memoriei pe Android.
Principalele puncte
LeakCanary — este o bibliotecă pentru detectarea automată a scurgerilor de memorie în aplicațiile Android, dezvoltată de compania Square. Se încorporează în procesul de construire a aplicației și urmărește automat când obiectele care ar trebui distruse (Activity, Fragment, View) rămân în memorie. La detectarea unei scurgeri, LeakCanary creează un heap dump și analizează lanțul de referințe care menține obiectul.
Biblioteca a devenit un standard în comunitatea Android: conform datelor GitHub, proiectul a strâns peste 28 de mii de stele și este utilizat în aplicațiile Google, Uber, Airbnb și Facebook. LeakCanary este disponibil în două versiuni principale: clasică 1.x (cu configurare manuală) și modernă 2.x (integrare automată prin ContentProvider). Versiunea 2.x nu necesită modificarea clasei Application — o singură dependență este suficientă pentru funcționare completă.
Sarcina principală a LeakCanary este detectarea situației în care un obiect continuă să existe în memorie după ce ciclul său de viață s-a încheiat. Acest lucru este tipic pentru scurgeri prin câmpuri statice, singleton-uri, callback-uri neînregistrate, clase anonime și închideri care captează obiecte externe.
Scurgerile de memorie pe Android sunt mai critice decât pe desktop din cauza volumului limitat de RAM pe dispozitivele mobile. Chiar și o scurgere de 5–10 MB la fiecare tranziție între ecrane poate duce la OutOfMemoryError după 30–40 de minute de utilizare a aplicației. LeakCanary detectează astfel de probleme în faza de dezvoltare, fără a aștepta o cădere în producție.
LeakCanary utilizează referințe slabe (WeakReference) în combinație cu apelul forțat al garbage collector-ului. Când Activity sau Fragment apelează onDestroy, LeakCanary creează un WeakReference pentru acest obiect și pornește GC după o scurtă întârziere (implicit 5 secunde). Dacă după GC obiectul este încă accesibil prin WeakReference, înseamnă că este menținut de o referință puternică — se înregistrează o scurgere.
După detectarea scurgerii, LeakCanary face un heap dump (captura completă a memoriei aplicației în format HPROF). Apoi analizorul încorporat (Shark pentru versiunea 2.x) construiește un graf de accesibilitate de la GC Roots până la obiectul scurs și găsește cea mai scurtă cale — lanțul de referințe care menține obiectul în memorie.
// Logica simplificată de detecție a 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 forțat
for (ref in watchedReferences) {
if (ref.get() != null) {
onLeakFound(ref) // obiectul a supraviețuit GC — aceasta este o scurgere
}
}
}
}
Momentul cheie — apelul forțat GcTrigger.runGc(). Fără el nu se poate distinge un obiect care chiar s-a scurs de un obiect pe care GC nu a apucat încă să îl colecteze. LeakCanary face acest lucru de până la trei ori: dacă după trei cicluri GC obiectul este încă în memorie — scurgerea este confirmată.
Shark — este analizorul de heap dump încorporat în LeakCanary 2.x, scris în Kotlin. Spre deosebire de analizorul anterior HAHA, Shark nu încarcă întregul fișier HPROF în memorie, ci parcurge graful său de obiecte cu alocări minime. Aceasta reduce consumul de RAM în timpul analizei de la 50 MB la 2–5 MB și scurtează timpul de analiză de la 30 de secunde la 1–3 secunde.
Instalarea LeakCanary 2.x într-un proiect Android modern durează o singură linie în build.gradle. Biblioteca utilizează ContentProvider pentru inițializare automată — nu este nevoie să modificați clasa Application sau să adăugați cod în MainActivity. Conexiunea se face doar pentru debug-build, pentru ca APK-ul de release să nu conțină cod inutil.
// build.gradle (app/module)
dependencies {
// debugImplementation — biblioteca doar pentru debug-build
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
}
După adăugarea dependenței și reconstruirea proiectului, LeakCanary apare automat în aplicație. La prima lansare, biblioteca afișează o notificare de sistem despre activare. Toate scurgerile detectate sunt afișate sub formă de notificări — clic pe notificare deschide ecranul cu raportul detaliat (LeakTrace).
Pentru personalizare puteți crea propriul AppWatcherInstaller și suprascrie parametrii: timeout-ul de așteptare GC, lista tipurilor de obiecte urmărite, activarea salvării heap dump pe disc. Cu toate acestea, pentru 90% dintre proiecte configurația implicită este optimă.
Începând cu versiunea 2.12, LeakCanary suportă urmărirea automată a ViewModel-urilor, scope-urilor de corutine și obiectelor State Compose. Nu sunt necesare dependențe suplimentare — biblioteca detectează singură ce componente Jetpack sunt utilizate în proiect și activează detectoarele corespunzătoare.
Raportul LeakCanary (LeakTrace) — este un lanț multi-linie de referințe de la GC Root până la obiectul scurs. Fiecare linie arată clasa și câmpul prin care trece referința puternică. Dezvoltatorul trebuie să citească lanțul de jos în sus: linia de jos — obiectul scurs, linia de sus — punctul de intrare (GC Root).
Un LeakTrace tipic arată astfel: GC Root → câmp static Application → singleton → callback → Activity. Dacă dezvoltatorul vede un astfel de lanț, problema este clară: singleton-ul menține un callback care a capturat o referință la Activity. Soluția — înlocuirea referinței puternice cu una slabă în 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)
╰
În acest exemplu LeakCanary arată că MainActivity este menținut prin lanțul: Application → ArrayList → MainActivity → CallbackWrapper → MyCallback → din nou MainActivity. Săgeata this$0 indică faptul că clasa anonimă MyCallback a capturat o referință externă la Activity. Soluția — faceți callback-ul o referință slabă sau anulați-l în onDestroy.
LeakCanary arată și starea scurgerii pentru fiecare element al lanțului: NO (nu este scurgere — acesta este elementul rădăcină), YES (obiectul ar trebui distrus), UNKNOWN (nu s-a putut determina starea). Statusul UNKNOWN nu înseamnă o problemă — este un obiect intermediar pe care LeakCanary nu îl poate clasifica fără echivoc.
Tranziția de la versiunea 1.x la 2.x a fost radicală: dezvoltatorii au rescris biblioteca de la zero, înlocuind vechiul analizor HAHA cu propriul motor Shark scris în Kotlin. Shark funcționează cu un ordin de mărime mai repede, necesită mai puțină memorie pentru analiză și determină mai precis cauzele principale ale scurgerilor.
| Parametru | LeakCanary 1.x | LeakCanary 2.x |
|---|---|---|
| Limbaj analizor | Java (HAHA — fork al Android SDK) | Kotlin (Shark — motor propriu) |
| Instalare | Configurare manuală AppWatcher în Application | Automată prin ContentProvider |
| Viteză | 10–30 secunde pentru analiza heap dump | 1–5 secunde pentru analiza heap dump |
| Performanță | Ocupă 10–50 MB RAM în timpul analizei | Ocupă 2–10 MB RAM în timpul analizei |
Avantajul cheie al Shark — nu încarcă întregul heap dump în memorie, ci parcurge graful său de referințe cu alocări minime. Acest lucru face LeakCanary 2.x potrivit pentru utilizarea pe dispozitive cu RAM redusă, fără riscul de OutOfMemoryError în timpul analizei.
În versiunea 2.x a apărut și posibilitatea de exportare a heap dump într-un fișier pentru analiză ulterioară în Android Studio Memory Profiler. Pentru aceasta, trebuie activată setarea dumpHeapWhenLeakFound în configurația AppWatcher.
LeakCanary detectează eficient mai multe clase de scurgeri caracteristice Android. Cel mai frecvent apare scurgerea prin referințe statice la Activity — dezvoltatorii păstrează o referință la contextul Activity într-un singleton, iar Activity nu poate fi colectată de GC după încheierea ciclului său de viață.
A doua categorie ca frecvență — scurgeri prin ascultători neînregistrați. Dacă în onStart a fost apelat registerListener, dar în onStop/onDestroy nu a fost apelat unregisterListener — obiectul-ascultător este menținut de sistem chiar și după distrugerea activității. LeakCanary arată fără echivoc care ascultător și în ce serviciu de sistem a rămas activ.
// Scurgere tipică: Activity capturată în callback-ul unui singleton
object AnalyticsManager {
private var callback: ((String) -> Unit)? = null
fun register(callback: (String) -> Unit) {
this.callback = callback // referință puternică la callback
}
fun unregister() {
callback = null // NU UITAȚI să apelați în onDestroy!
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
AnalyticsManager.register { event ->
logEvent(event) // lambda capturează this
}
// dacă nu apelați unregister în onDestroy → scurgere Activity
}
}
A treia categorie — scurgeri prin Fragment în BackStack. Dacă FragmentTransaction.addToBackStack() este apelat fără eliminarea Fragmentului la revenire, instanțele vechi de Fragment rămân în memorie. LeakCanary ajută la detectarea acestor scurgeri ascunse în fazele incipiente ale dezvoltării.
Pentru fiecare scurgere detectată, LeakCanary oferă o descriere și recomandări de remediere. În versiunea 2.14 a fost adăugată integrarea cu Android Lint — biblioteca poate crea automat sarcini în issue tracker la detectarea unei scurgeri în CI.
Întrebări frecvente
Da, obligatoriu. LeakCanary se conectează prin debugImplementation în build.gradle, ceea ce îl exclude automat din construcția de release. Dacă îl conectați prin implementation, biblioteca va intra în APK-ul de release și va afișa scurgeri utilizatorilor finali — acest lucru este inacceptabil.
Impactul asupra performanței este minim. LeakCanary se activează doar după onDestroy al componentei și nu intervine în randarea UI sau procesarea atingerilor. Singurul cost — o scurtă pauză a GC forțat (aproximativ 100 ms) și scrierea heap dump la scurgere (sutimi de secundă).
LeakCanary salvează automat heap dump în format HPROF în folderul aplicației. Fișierul poate fi exportat prin Android Studio: Device File Explorer → data/data/com.example/files/leakcanary/. Pentru vizualizare, deschideți fișierul în Memory Profiler prin Capture → Open Heap Dump.
Da, începând cu versiunea 2.12 LeakCanary suportă complet Jetpack Compose. Biblioteca urmărește contextele Composition și obiectele State, detectând automat scurgerile în funcțiile Composable. Nu este necesară o configurare separată — funcționează din cutie.
Alarme false sunt posibile, dar rare. LeakCanary utilizează un triplu apel GC înainte de declararea scurgerii, ceea ce elimină majoritatea falsurilor pozitive. Dacă credeți că alarma este falsă — creați un IgnoredReference pentru clasa specifică în configurație.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și