LeakCanary — що це таке, бібліотека для пошуку витоків в Android

Автор: IT Sectr Опубліковано: 2026-03-30 Час читання: 9 хв

LeakCanary — це бібліотека з відкритим кодом від Square для автоматичного виявлення витоків пам'яті в Android-додатках. Вона інтегрується в процес розробки та в реальному часі відстежує життєвий цикл Activity, Fragment, ViewModel та інших компонентів, сигналізуючи про витоки одразу після їх виникнення. За даними Square Open Source, бібліотека використовується в тисячах проектів і вважається стандартом де-факто для діагностики пам'яті на Android.

Головне

  • LeakCanary — бібліотека для автоматичного виявлення витоків пам'яті на Android.
  • Механізм роботи заснований на WeakReference і ручному запуску GC після знищення компонента.
  • Heap dump створюється автоматично при виявленні витоку та аналізується вбудованим аналізатором.
  • Результат — точний ланцюжок посилань (leak trace), що вказує на місце витоку в коді.
  • LeakCanary 2.x не вимагає ручного налаштування — достатньо однієї залежності в build.gradle.

Що таке LeakCanary?

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 — виявити ситуацію, коли об'єкт продовжує існувати в пам'яті після того, як його життєвий цикл завершено. Це типово для витоків через статичні поля, синглтони, незареєстровані колбеки, анонімні класи та замикання, що захоплюють зовнішні об'єкти.

Чому LeakCanary важливий для Android-розробки

Витоки пам'яті на Android критичніші, ніж на десктопі, через обмежений обсяг RAM на мобільних пристроях. Навіть витік у 5–10 МБ на кожному переході між екранами може призвести до OutOfMemoryError через 30–40 хвилин використання додатка. LeakCanary виявляє такі проблеми на стадії розробки, не чекаючи падіння на продакшені.

Як працює LeakCanary?

LeakCanary використовує слабкі посилання (WeakReference) у комбінації з примусовим викликом збирача сміття. Коли Activity або Fragment викликають onDestroy, LeakCanary створює WeakReference на цей об'єкт і запускає GC через коротку затримку (за замовчуванням 5 секунд). Якщо після GC об'єкт все ще доступний через WeakReference, значить, він утримується сильним посиланням — фіксується витік.

Після виявлення витоку LeakCanary робить heap dump (дамп купи) — повний знімок пам'яті додатка у форматі HPROF. Далі вбудований аналізатор (Shark для версії 2.x) будує граф досяжності від GC Roots до об'єкта, що витік, і знаходить найкоротший шлях — ланцюжок посилань, що утримує об'єкт у пам'яті.

kotlin
// Спрощена логіка детекції 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

Shark — це вбудований в LeakCanary 2.x аналізатор heap dump, написаний на Kotlin. На відміну від попереднього аналізатора HAHA, Shark не завантажує весь HPROF-файл у пам'ять, а обходить його граф об'єктів з мінімальними алокаціями. Це знижує споживання RAM при аналізі з 50 MB до 2–5 MB і скорочує час аналізу з 30 секунд до 1–3 секунд.

Як встановити та налаштувати LeakCanary?

Встановлення LeakCanary 2.x в сучасний Android-проект займає один рядок у build.gradle. Бібліотека використовує ContentProvider для автоматичної ініціалізації — не потрібно правити Application-клас або додавати якийсь код у MainActivity. Підключення виконується тільки для debug-збірки, щоб у релізному APK не було зайвого коду.

groovy
// build.gradle (app/module)
dependencies {
    // debugImplementation — бібліотека тільки для debug-збірки
    debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
}

Після додавання залежності та перезбірки проекту LeakCanary автоматично з'являється в додатку. При першому запуску бібліотека показує системне сповіщення про активацію. Всі виявлені витоки відображаються у вигляді сповіщень — натискання на сповіщення відкриває екран з детальним звітом (LeakTrace).

Для кастомізації можна створити власний AppWatcherInstaller і перевизначити параметри: таймаут очікування GC, список відстежуваних типів об'єктів, включення збереження heap dump на диск. Однак для 90% проектів конфігурація за замовчуванням є оптимальною.

Налаштування для корутин і Jetpack Compose

Починаючи з версії 2.12, LeakCanary підтримує автоматичне відстеження ViewModel, корутинних скопів та State-об'єктів Compose. Додаткових залежностей не потрібно — бібліотека сама визначає, які компоненти Jetpack використовуються в проекті, та активує відповідні детектори.

Як читати звіт LeakCanary

Звіт LeakCanary (LeakTrace) — це багаторядковий ланцюжок посилань від GC Root до об'єкта, що витік. Кожен рядок показує клас і поле, через яке проходить сильне посилання. Розробнику потрібно читати ланцюжок знизу вгору: нижній рядок — об'єкт, що витік, верхній — точка входу (GC Root).

Типовий LeakTrace виглядає так: GC Root → статичне поле Application → синглтон → колбек → Activity. Якщо розробник бачить такий ланцюжок, проблема ясна: синглтон тримає колбек, який захопив посилання на Activity. Рішення — замінити сильне посилання на слабке в синглтоні.

text
┬
├─ 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 не може однозначно класифікувати.

LeakCanary 2.x проти 1.x: ключові відмінності

Перехід з версії 1.x на 2.x був кардинальним: розробники переписали бібліотеку з нуля, замінивши застарілий HAHA-аналізатор на власний рушій Shark, написаний на Kotlin. Shark працює на порядок швидше, потребує менше пам'яті для аналізу та точніше визначає корінні причини витоків.

ПараметрLeakCanary 1.xLeakCanary 2.x
Мова аналізатораJava (HAHA — fork of Android SDK)Kotlin (Shark — власний рушій)
ВстановленняРучне налаштування AppWatcher в ApplicationАвтоматичне через ContentProvider
Швидкість10–30 секунд на аналіз heap dump1–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

LeakCanary ефективно виявляє кілька класів витоків, характерних для Android. Найчастіше зустрічається витік через статичні посилання на Activity — розробники зберігають посилання на контекст Activity у синглтоні, і Activity не може бути зібрана GC після завершення її життєвого циклу.

Друга за частотою категорія — витоки через незареєстровані слухачі. Якщо в onStart був викликаний registerListener, але в onStop/onDestroy не викликаний unregisterListener — об'єкт-слухач утримується системою навіть після знищення активності. LeakCanary однозначно показує, який слухач і в якому системному сервісі залишився живим.

kotlin
// Типовий витік: 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 з релізного APK?

Так, обов'язково. LeakCanary підключається через debugImplementation в build.gradle, що автоматично виключає його з релізної збірки. Якщо підключити через implementation, бібліотека потрапить у релізний APK і буде показувати витоки кінцевим користувачам — це неприпустимо.

Чи уповільнює LeakCanary роботу додатка?

Вплив на продуктивність мінімальний. LeakCanary активується тільки після onDestroy компонента і не втручається у відображення UI або обробку дотиків. Єдина витрата — коротка пауза примусового GC (близько 100 мс) і запис heap dump при витоку (соті частки секунди).

Як експортувати звіт LeakCanary?

LeakCanary автоматично зберігає heap dump у форматі HPROF у папку додатка. Файл можна експортувати через Android Studio: Device File Explorer → data/data/com.example/files/leakcanary/. Для перегляду відкрийте файл у Memory Profiler через Capture → Open Heap Dump.

Чи працює LeakCanary з Jetpack Compose?

Так, починаючи з версії 2.12 LeakCanary повністю підтримує Jetpack Compose. Бібліотека відстежує Composition-контексти та State-об'єкти, автоматично визначаючи витоки в Composable-функціях. Окреме налаштування не потрібне — працює з коробки.

Чи може LeakCanary помилятися (false positive)?

Хибні спрацьовування можливі, але рідкісні. LeakCanary використовує потрійний виклик GC перед оголошенням витоку, що виключає більшість false positive. Якщо ви вважаєте, що спрацьовування хибне — створіть IgnoredReference для конкретного класу в конфігурації.

Підсумки

  • LeakCanary — стандартна бібліотека для автоматичного виявлення витоків пам'яті в Android-додатках.
  • Бібліотека використовує WeakReference і примусовий GC для детекції об'єктів, що пережили свій життєвий цикл.
  • Heap dump аналізується вбудованим рушієм Shark, який будує ланцюжок посилань від GC Root до об'єкта, що витік.
  • Встановлення в сучасному проекті — один рядок у build.gradle: debugImplementation.
  • LeakCanary 2.x повністю переписаний на Kotlin і працює в 5–10 разів швидше за попередню версію.
  • Найпоширеніші витоки: статичні посилання на Activity, незареєстровані слухачі та Fragment в BackStack.
  • Впровадьте LeakCanary у debug-збірку кожного проекту — це запобігає витокам на продакшені.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також