LeakCanary — egy nyílt forráskódú könyvtár a Square-től az Android-alkalmazások memóriaszivárgásainak automatikus észlelésére. Beépül a fejlesztési folyamatba, és valós időben követi az Activity, Fragment, ViewModel és más komponensek életciklusát, jelezve a szivárgásokat azok előfordulása után azonnal. A Square Open Source adatai szerint a könyvtárat több ezer projektben használják, és ez a de facto szabvány a memóriadiagnosztikában Androidon.
Főbb pontok
LeakCanary — egy könyvtár a memóriaszivárgások automatikus észlelésére Android-alkalmazásokban, amelyet a Square fejlesztett. Beépül az alkalmazás építési folyamatába, és automatikusan figyeli, hogy a megsemmisítendő objektumok (Activity, Fragment, View) a memóriában maradnak-e. Szivárgás észlelésekor a LeakCanary heap dump-ot készít, és elemzi az objektumot tartó referencialáncot.
A könyvtár szabvánnyá vált az Android közösségben: a GitHub adatai szerint a projekt több mint 28 ezer csillagot gyűjtött, és használják a Google, Uber, Airbnb és Facebook alkalmazásai. A LeakCanary két fő verzióban érhető el: klasszikus 1.x (kézi beállítással) és modern 2.x (automatikus integráció ContentProvider-en keresztül). A 2.x verzió nem igényli az Application osztály módosítását — egy függőség elegendő a teljes működéshez.
A LeakCanary fő feladata annak a helyzetnek az észlelése, amikor egy objektum az életciklusának befejeződése után is a memóriában marad. Ez jellemző a statikus mezőkön, singletonokon, nem regisztrált callbackeken, névtelen osztályokon és a külső objektumokat befogó zárványokon keresztüli szivárgásokra.
A memóriaszivárgások Androidon kritikusabbak, mint asztali gépen a mobil eszközök korlátozott RAM-ja miatt. Még egy 5–10 MB-os szivárgás is minden képernyőváltáskor OutOfMemoryError-hoz vezethet 30–40 perc használat után. A LeakCanary az ilyen problémákat a fejlesztési fázisban észleli, nem várva a termelésbeli összeomlásra.
LeakCanary gyenge referenciákat (WeakReference) használ a szemétgyűjtő kényszerített meghívásával kombinálva. Amikor az Activity vagy Fragment meghívja az onDestroy-t, a LeakCanary létrehoz egy WeakReference-t erre az objektumra, és rövid késleltetés után (alapértelmezetten 5 másodperc) elindítja a GC-t. Ha a GC után az objektum még mindig elérhető a WeakReference-en keresztül, akkor egy erős referencia tartja — szivárgás kerül rögzítésre.
A szivárgás észlelése után a LeakCanary heap dump-ot (az alkalmazás memóriájának teljes pillanatképét HPROF formátumban) készít. Ezután a beépített elemző (Shark a 2.x verzióhoz) elérhetőségi gráfot épít a GC Roots-tól a kiszivárgott objektumig, és megtalálja a legrövidebb utat — az objektumot a memóriában tartó referencialáncot.
// A LeakCanary észlelési logikájának egyszerűsített változata
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() // kényszerített GC
for (ref in watchedReferences) {
if (ref.get() != null) {
onLeakFound(ref) // az objektum túlélte a GC-t — ez szivárgás
}
}
}
}
A kulcspont — a GcTrigger.runGc() kényszerített meghívása. Enélkül lehetetlen megkülönböztetni egy ténylegesen kiszivárgott objektumot attól, amelyet a GC még nem gyűjtött be. A LeakCanary ezt háromszor teszi meg: ha három GC-ciklus után az objektum még mindig a memóriában van — a szivárgás megerősítést nyer.
Shark — a LeakCanary 2.x-be épített heap dump elemző, amely Kotlin nyelven íródott. Az előző HAHA elemzővel ellentétben a Shark nem tölti be a teljes HPROF fájlt a memóriába, hanem minimális foglalásokkal járja be az objektumgráfját. Ez csökkenti a RAM-fogyasztást elemzés közben 50 MB-ról 2–5 MB-ra, és lerövidíti az elemzési időt 30 másodpercről 1–3 másodpercre.
A LeakCanary 2.x telepítése egy modern Android projektben egy sort vesz igénybe a build.gradle-ben. A könyvtár a ContentProvider-t használja az automatikus inicializáláshoz — nem kell módosítani az Application osztályt vagy kódot hozzáadni a MainActivity-hez. A csatlakozás csak debug-build esetén történik, hogy a release APK-ban ne legyen felesleges kód.
// build.gradle (app/module)
dependencies {
// debugImplementation — könyvtár csak debug-build-hez
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
}
A függőség hozzáadása és a projekt újraépítése után a LeakCanary automatikusan megjelenik az alkalmazásban. Az első indításkor a könyvtár rendszerértesítést jelenít meg az aktiválásról. Az összes észlelt szivárgás értesítésként jelenik meg — az értesítésre kattintva megnyílik a részletes jelentés (LeakTrace) képernyője.
Testreszabáshoz létrehozhatja saját AppWatcherInstaller-jét, és felülírhatja a paramétereket: GC várakozási időtúllépés, a követett objektumtípusok listája, a heap dump lemezre mentésének engedélyezése. A projektek 90%-ánál azonban az alapértelmezett konfiguráció az optimális.
A 2.12-es verziótól kezdve a LeakCanary támogatja a ViewModel, a korutinszkópok és a Compose State objektumainak automatikus követését. Nincs szükség további függőségekre — a könyvtár maga érzékeli, hogy mely Jetpack-komponensek vannak használatban a projektben, és aktiválja a megfelelő detektorokat.
A LeakCanary jelentés (LeakTrace) — egy többsoros referencialánc a GC Root-tól a kiszivárgott objektumig. Minden sor azt az osztályt és mezőt mutatja, amelyen keresztül az erős referencia halad. A fejlesztőnek alulról felfelé kell olvasnia a láncot: alsó sor — kiszivárgott objektum, felső sor — belépési pont (GC Root).
Egy tipikus LeakTrace így néz ki: GC Root → statikus Application mező → singleton → callback → Activity. Ha a fejlesztő ilyen láncot lát, a probléma világos: a singleton tart egy callback-et, amely befogott egy referenciát az Activity-re. Megoldás — az erős referencia cseréje gyengére a singletonban.
┬
├─ 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)
╰
Ebben a példában a LeakCanary azt mutatja, hogy a MainActivity-t a lánc tartja: Application → ArrayList → MainActivity → CallbackWrapper → MyCallback → vissza MainActivity. A this$0 nyíl jelzi, hogy a névtelen MyCallback osztály befogott egy külső referenciát az Activity-re. Megoldás — tegye a callback-et gyenge referenciává, vagy vonja vissza onDestroy-ben.
A LeakCanary a lánc minden elemére mutatja a szivárgás állapotát is: NO (nincs szivárgás — ez a gyökérelem), YES (az objektumot meg kell semmisíteni), UNKNOWN (az állapot nem határozható meg). Az UNKNOWN állapot nem jelent problémát — ez egy köztes objektum, amelyet a LeakCanary nem tud egyértelműen besorolni.
Az 1.x verzióról a 2.x-re való áttérés radikális volt: a fejlesztők a nulláról írták újra a könyvtárat, lecserélve az elavult HAHA elemzőt a saját Shark motorra, amely Kotlin nyelven íródott. A Shark nagyságrenddel gyorsabban működik, kevesebb memóriát igényel az elemzéshez, és pontosabban határozza meg a szivárgások kiváltó okait.
| Paraméter | LeakCanary 1.x | LeakCanary 2.x |
|---|---|---|
| Elemző nyelve | Java (HAHA — az Android SDK fork-ja) | Kotlin (Shark — saját motor) |
| Telepítés | AppWatcher kézi konfigurálása az Application-ben | Automatikus ContentProvider-en keresztül |
| Sebesség | 10–30 másodperc a heap dump elemzéséhez | 1–5 másodperc a heap dump elemzéséhez |
| Teljesítmény | 10–50 MB RAM-ot foglal elemzés közben | 2–10 MB RAM-ot foglal elemzés közben |
A Shark fő előnye — nem tölti be a teljes heap dump-ot a memóriába, hanem minimális foglalásokkal járja be a referenciagráfját. Ez alkalmassá teszi a LeakCanary 2.x-et alacsony RAM-mal rendelkező eszközökön való használatra, az OutOfMemoryError kockázata nélkül az elemzés során.
A 2.x verzióban megjelent a heap dump fájlba exportálásának lehetősége is későbbi elemzéshez az Android Studio Memory Profiler-ben. Ehhez engedélyezni kell a dumpHeapWhenLeakFound beállítást az AppWatcher konfigurációjában.
A LeakCanary hatékonyan észleli az Androidra jellemző szivárgások több osztályát. A leggyakoribb az Activity-re mutató statikus referenciákon keresztüli szivárgás — a fejlesztők eltárolják az Activity kontextusára mutató referenciát egy singletonban, és az Activity-t nem tudja összegyűjteni a GC az életciklusának befejeződése után.
A második leggyakoribb kategória — nem regisztrált hallgatókon keresztüli szivárgások. Ha az onStart-ban meghívták a registerListener-t, de az onStop/onDestroy-ben nem hívták meg az unregisterListener-t — a hallgató objektumot a rendszer még az aktivitás megsemmisítése után is tartja. A LeakCanary egyértelműen mutatja, melyik hallgató és melyik rendszerszolgáltatásban maradt aktív.
// Tipikus szivárgás: Activity elkapva egy singleton callback-jében
object AnalyticsManager {
private var callback: ((String) -> Unit)? = null
fun register(callback: (String) -> Unit) {
this.callback = callback // erős referencia a callback-re
}
fun unregister() {
callback = null // NE FELEJTSE EL meghívni onDestroy-ben!
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
AnalyticsManager.register { event ->
logEvent(event) // lambda elkapja a this-t
}
// ha nem hívja meg az unregister-t onDestroy-ben → Activity szivárgás
}
}
A harmadik kategória — szivárgások Fragment-en keresztül a BackStack-ben. Ha a FragmentTransaction.addToBackStack() a Fragment eltávolítása nélkül kerül meghívásra visszatéréskor, a régi Fragment példányok a memóriában maradnak. A LeakCanary segít az ilyen rejtett szivárgások korai felderítésében a fejlesztés során.
Minden észlelt szivárgáshoz a LeakCanary leírást és javítási javaslatokat kínál. A 2.14-es verzióban integráció került hozzáadásra az Android Lint-tel — a könyvtár automatikusan feladatokat hozhat létre az issue tracker-ben, ha szivárgást észlel a CI-ben.
Gyakran ismételt kérdések
Igen, feltétlenül. A LeakCanary a debugImplementation-en keresztül csatlakozik a build.gradle-ben, ami automatikusan kizárja a release buildből. Ha az implementation-en keresztül csatlakoztatja, a könyvtár bekerül a release APK-ba, és szivárgásokat fog mutatni a végfelhasználóknak — ez elfogadhatatlan.
A teljesítményre gyakorolt hatás minimális. A LeakCanary csak a komponens onDestroy-je után aktiválódik, és nem avatkozik be a UI megjelenítésébe vagy az érintések feldolgozásába. Az egyetlen költség — a kényszerített GC rövid szünete (körülbelül 100 ms) és a heap dump írása szivárgáskor (másodperc századrészei).
A LeakCanary automatikusan elmenti a heap dump-ot HPROF formátumban az alkalmazás mappájába. A fájl exportálható az Android Studio-n keresztül: Device File Explorer → data/data/com.example/files/leakcanary/. Megtekintéshez nyissa meg a fájlt a Memory Profiler-ben a Capture → Open Heap Dump segítségével.
Igen, a 2.12-es verziótól kezdve a LeakCanary teljes mértékben támogatja a Jetpack Compose-t. A könyvtár követi a Composition kontextusokat és az State objektumokat, automatikusan észlelve a szivárgásokat a Composable függvényekben. Külön konfiguráció nem szükséges — a dobozból kivéve működik.
Téves riasztások lehetségesek, de ritkák. A LeakCanary háromszori GC-meghívást használ a szivárgás bejelentése előtt, ami kiküszöböli a legtöbb téves pozitív eredményt. Ha úgy gondolja, hogy a riasztás téves — hozzon létre egy IgnoredReference-t az adott osztályhoz a konfigurációban.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is