LeakCanary — е библиотека с отворен код от Square за автоматично откриване на изтичания на памет в Android приложения. Тя се интегрира в процеса на разработка и в реално време проследява жизнения цикъл на Activity, Fragment, ViewModel и други компоненти, сигнализирайки за изтичания веднага след възникването им. Според данни на Square Open Source, библиотеката се използва в хиляди проекти и се счита за де факто стандарт за диагностика на паметта в Android.
Основни точки
LeakCanary — е библиотека за автоматично откриване на изтичания на памет в Android приложения, разработена от компанията Square. Тя се вгражда в процеса на изграждане на приложението и автоматично проследява кога обекти, които трябва да бъдат унищожени (Activity, Fragment, View), остават в паметта. При откриване на изтичане, LeakCanary създава heap dump и анализира веригата от референции, задържаща обекта.
Библиотеката се превърна в стандарт в Android общността: според данни на GitHub, проектът е събрал над 28 хиляди звезди и се използва в приложения на Google, Uber, Airbnb и Facebook. LeakCanary е достъпен в две основни версии: класическа 1.x (с ръчна конфигурация) и модерна 2.x (автоматична интеграция чрез ContentProvider). Версия 2.x не изисква промяна на Application класа — една зависимост е достатъчна за пълноценна работа.
Основната задача на LeakCanary е да открие ситуация, при която обект продължава да съществува в паметта след завършване на жизнения си цикъл. Това е типично за изтичания чрез статични полета, сингълтъни, нерегистрирани колбеци, анонимни класове и затваряния, улавящи външни обекти.
Изтичанията на памет в Android са по-критични, отколкото на десктоп, поради ограниченото количество RAM на мобилните устройства. Дори изтичане от 5–10 MB при всяка смяна на екрана може да доведе до OutOfMemoryError след 30–40 минути използване на приложението. LeakCanary открива такива проблеми на етапа на разработка, без да чака срив в продукция.
LeakCanary използва слаби референции (WeakReference) в комбинация с принудително извикване на garbage collector. Когато Activity или Fragment извика onDestroy, LeakCanary създава WeakReference към този обект и стартира GC след кратко закъснение (по подразбиране 5 секунди). Ако след GC обектът все още е достъпен чрез WeakReference, това означава, че се задържа от силна референция — изтичането се записва.
След откриване на изтичане, LeakCanary прави heap dump (пълен моментен снимка на паметта на приложението в HPROF формат). След това вграденият анализатор (Shark за версия 2.x) изгражда граф на достижимост от GC Roots до изтеклия обект и намира най-краткия път — веригата от референции, задържаща обекта в паметта.
// Опростена логика за откриване на 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 (ref in watchedReferences) {
if (ref.get() != null) {
onLeakFound(ref) // обектът преживя GC — това е изтичане
}
}
}
}
Ключовият момент — принудителното извикване на GcTrigger.runGc(). Без него е невъзможно да се различи обект, който наистина е изтекъл, от обект, който GC все още не е успял да събере. LeakCanary прави това до три пъти: ако след три GC цикъла обектът все още е в паметта — изтичането е потвърдено.
Shark — е вграденият анализатор на heap dump в LeakCanary 2.x, написан на Kotlin. За разлика от предишния анализатор HAHA, Shark не зарежда целия HPROF файл в паметта, а обхожда неговия обектен граф с минимални алокации. Това намалява консумацията на RAM по време на анализ от 50 MB на 2–5 MB и съкращава времето за анализ от 30 секунди на 1–3 секунди.
Инсталацията на LeakCanary 2.x в модерен Android проект отнема един ред в build.gradle. Библиотеката използва ContentProvider за автоматично инициализиране — не е необходимо да променяте Application класа или да добавяте код в MainActivity. Свързването се извършва само за debug-build, за да няма излишен код в release APK.
// build.gradle (app/module)
dependencies {
// debugImplementation — библиотека само за debug-build
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
}
След добавяне на зависимостта и повторно изграждане на проекта, LeakCanary автоматично се появява в приложението. При първото стартиране библиотеката показва системна нотификация за активиране. Всички открити изтичания се показват като нотификации — клик върху нотификацията отваря екрана с подробния отчет (LeakTrace).
За персонализиране можете да създадете свой собствен AppWatcherInstaller и да презапишете параметрите: време за изчакване на GC, списък на проследяваните типове обекти, активиране на запазване на heap dump на диск. Въпреки това, за 90% от проектите конфигурацията по подразбиране е оптимална.
От версия 2.12 нататък, LeakCanary поддържа автоматично проследяване на ViewModel, корутинни скоупове и State обекти на Compose. Не са необходими допълнителни зависимости — библиотеката сама определя кои Jetpack компоненти се използват в проекта и активира съответните детектори.
Отчетът на LeakCanary (LeakTrace) — е многоредова верига от референции от GC Root до изтеклия обект. Всеки ред показва класа и полето, през които преминава силната референция. Разработчикът трябва да чете веригата отдолу нагоре: долният ред — изтекъл обект, горният ред — входна точка (GC Root).
Типичен LeakTrace изглежда така: GC Root → статично поле Application → сингълтън → колбек → Activity. Ако разработчикът види такава верига, проблемът е ясен: сингълтънът държи колбек, който е уловил референция към Activity. Решение — заменете силната референция със слаба в сингълтъна.
┬
├─ 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)
╰
В този пример LeakCanary показва, че MainActivity се задържа чрез веригата: Application → ArrayList → MainActivity → CallbackWrapper → MyCallback → отново MainActivity. Стрелката this$0 показва, че анонимният клас MyCallback е уловил външна референция към Activity. Решение — направете колбека слаба референция или го отменете в onDestroy.
LeakCanary също така показва статус на изтичане за всеки елемент от веригата: NO (няма изтичане — това е коренов елемент), YES (обектът трябва да бъде унищожен), UNKNOWN (статусът не може да бъде определен). Статус UNKNOWN не означава проблем — това е междинен обект, който LeakCanary не може еднозначно да класифицира.
Преходът от версия 1.x към 2.x беше радикален: разработчиците преработиха библиотеката от нулата, заменяйки остарелия HAHA анализатор със собствен двигател Shark, написан на Kotlin. Shark работи значително по-бързо, изисква по-малко памет за анализ и по-точно определя основните причини за изтичания.
| Параметър | LeakCanary 1.x | LeakCanary 2.x |
|---|---|---|
| Език на анализатора | Java (HAHA — fork на Android SDK) | Kotlin (Shark — собствен двигател) |
| Инсталация | Ръчна конфигурация на AppWatcher в Application | Автоматична чрез ContentProvider |
| Скорост | 10–30 секунди за анализ на heap dump | 1–5 секунди за анализ на heap dump |
| Производителност | Заема 10–50 MB RAM по време на анализ | Заема 2–10 MB RAM по време на анализ |
Ключовото предимство на Shark — не зарежда целия heap dump в паметта, а обхожда неговия референтен граф с минимални алокации. Това прави LeakCanary 2.x подходящ за използване на устройства с ниско количество RAM без риск от OutOfMemoryError по време на анализ.
Във версия 2.x също така се появи възможност за експортиране на heap dump във файл за последващ анализ в Android Studio Memory Profiler. За целта трябва да активирате настройката dumpHeapWhenLeakFound в конфигурацията на AppWatcher.
LeakCanary ефективно открива няколко класа изтичания, характерни за Android. Най-често срещано е изтичането чрез статични референции към Activity — разработчиците съхраняват референция към контекста на Activity в сингълтън и Activity не може да бъде събрана от GC след завършване на жизнения си цикъл.
Втората по честота категория — изтичания чрез нерегистрирани слушатели. Ако в onStart е бил извикан registerListener, но в onStop/onDestroy не е бил извикан unregisterListener — обектът-слушател се задържа от системата дори след унищожаване на активността. LeakCanary недвусмислено показва кой слушател и в коя системна услуга е останал активен.
// Типично изтичане: Activity уловена в колбек на сингълтън
object AnalyticsManager {
private var callback: ((String) -> Unit)? = null
fun register(callback: (String) -> Unit) {
this.callback = callback // силна референция към колбек
}
fun unregister() {
callback = null // НЕ ЗАБРАВЯЙТЕ да извикате в onDestroy!
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
AnalyticsManager.register { event ->
logEvent(event) // ламбда улавя this
}
// ако не извикате unregister в onDestroy → изтичане на Activity
}
}
Третата категория — изтичания чрез Fragment в BackStack. Ако FragmentTransaction.addToBackStack() се извиква без премахване на Fragment при връщане, старите инстанции на Fragment остават в паметта. LeakCanary помага да се открият такива скрити изтичания в ранните етапи на разработка.
За всяко открито изтичане LeakCanary предлага описание и препоръки за поправка. Във версия 2.14 е добавена интеграция с Android Lint — библиотеката може автоматично да създава задачи в issue tracker при откриване на изтичане в CI.
Често задавани въпроси
Да, задължително. LeakCanary се свързва чрез debugImplementation в build.gradle, което автоматично го изключва от release версията. Ако го свържете чрез implementation, библиотеката ще попадне в release APK и ще показва изтичания на крайните потребители — това е недопустимо.
Влиянието върху производителността е минимално. LeakCanary се активира само след onDestroy на компонента и не пречи на изобразяването на UI или обработката на докосвания. Единственият разход — кратка пауза на принудителния GC (около 100 ms) и запис на heap dump при изтичане (стотни от секундата).
LeakCanary автоматично запазва heap dump в HPROF формат в папката на приложението. Файлът може да бъде експортиран чрез Android Studio: Device File Explorer → data/data/com.example/files/leakcanary/. За преглед отворете файла в Memory Profiler чрез Capture → Open Heap Dump.
Да, от версия 2.12 LeakCanary напълно поддържа Jetpack Compose. Библиотеката проследява Composition контексти и State обекти, автоматично откривайки изтичания в Composable функции. Отделна конфигурация не е необходима — работи веднага.
Фалшиви активации са възможни, но редки. LeakCanary използва троен GC цикъл преди обявяване на изтичане, което елиминира повечето фалшиво положителни резултати. Ако смятате, че активацията е фалшива — създайте IgnoredReference за конкретния клас в конфигурацията.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също