LeakCanary — to biblioteka open source od Square do automatycznego wykrywania wycieków pamięci w aplikacjach Android. Integruje się z procesem programowania i w czasie rzeczywistym śledzi cykl życia Activity, Fragment, ViewModel i innych komponentów, sygnalizując wycieki natychmiast po ich wystąpieniu. Według danych Square Open Source, biblioteka jest używana w tysiącach projektów i uważana za standard de facto w diagnostyce pamięci na Androidzie.
Najważniejsze
LeakCanary — to biblioteka do automatycznego wykrywania wycieków pamięci w aplikacjach Android, opracowana przez firmę Square. Wbudowuje się w proces budowania aplikacji i automatycznie śledzi, kiedy obiekty, które powinny zostać zniszczone (Activity, Fragment, View), pozostają w pamięci. Po wykryciu wycieku LeakCanary tworzy zrzut sterty i analizuje łańcuch referencji utrzymujących obiekt.
Biblioteka stała się standardem w społeczności Android: według danych GitHub projekt zebrał ponad 28 tysięcy gwiazdek i jest używany w aplikacjach Google, Uber, Airbnb i Facebook. LeakCanary jest dostępny w dwóch głównych wersjach: klasyczna 1.x (z ręczną konfiguracją) i nowoczesna 2.x (automatyczna integracja przez ContentProvider). Wersja 2.x nie wymaga modyfikacji klasy Application — sama zależność wystarczy do pełnego działania.
Głównym zadaniem LeakCanary jest wykrycie sytuacji, gdy obiekt nadal istnieje w pamięci po zakończeniu swojego cyklu życia. Jest to typowe dla wycieków przez pola statyczne, singletonów, niezarejestrowanych callbacków, klas anonimowych i domknięć przechwytujących obiekty zewnętrzne.
Wycieki pamięci na Androidzie są bardziej krytyczne niż na desktopie ze względu na ograniczoną ilość RAM w urządzeniach mobilnych. Nawet wyciek 5–10 MB przy każdej zmianie ekranu może doprowadzić do OutOfMemoryError po 30–40 minutach użytkowania aplikacji. LeakCanary wykrywa takie problemy na etapie programowania, nie czekając na awarię na produkcji.
LeakCanary używa słabych referencji (WeakReference) w połączeniu z wymuszonym wywołaniem garbage collectora. Gdy Activity lub Fragment wywołuje onDestroy, LeakCanary tworzy WeakReference na ten obiekt i uruchamia GC po krótkim opóźnieniu (domyślnie 5 sekund). Jeśli po GC obiekt jest nadal dostępny przez WeakReference, oznacza to, że jest utrzymywany przez silną referencję — wykryto wyciek.
Po wykryciu wycieku LeakCanary wykonuje zrzut sterty — pełny obraz pamięci aplikacji w formacie HPROF. Następnie wbudowany analizator (Shark dla wersji 2.x) buduje graf osiągalności od GC Roots do wyciekającego obiektu i znajduje najkrótszą ścieżkę — łańcuch referencji utrzymujących obiekt w pamięci.
// Uproszczona logika detekcji 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() // wymuszony GC
for (ref in watchedReferences) {
if (ref.get() != null) {
onLeakFound(ref) // obiekt przeżył GC — to wyciek
}
}
}
}
Kluczowym momentem jest wymuszone wywołanie GcTrigger.runGc(). Bez niego nie można odróżnić obiektu, który rzeczywiście wyciekł, od obiektu, którego GC jeszcze nie zdążył zebrać. LeakCanary robi to do trzech razy: jeśli po trzech cyklach GC obiekt jest nadal w pamięci — wyciek jest potwierdzony.
Shark — to wbudowany w LeakCanary 2.x analizator zrzutów sterty napisany w Kotlinie. W przeciwieństwie do poprzedniego analizatora HAHA, Shark nie ładuje całego pliku HPROF do pamięci, ale przegląda jego graf obiektów z minimalnymi alokacjami. Zmniejsza to zużycie RAM podczas analizy z 50 MB do 2–5 MB i skraca czas analizy z 30 sekund do 1–3 sekund.
Instalacja LeakCanary 2.x w nowoczesnym projekcie Android zajmuje jedną linię w build.gradle. Biblioteka używa ContentProvider do automatycznej inicjalizacji — nie trzeba modyfikować klasy Application ani dodawać żadnego kodu w MainActivity. Podłączenie wykonuje się tylko dla debug-build, aby w wydaniowym APK nie było zbędnego kodu.
// build.gradle (app/module)
dependencies {
// debugImplementation — biblioteka tylko dla debug-build
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
}
Po dodaniu zależności i przebudowaniu projektu LeakCanary automatycznie pojawia się w aplikacji. Przy pierwszym uruchomieniu biblioteka pokazuje systemowe powiadomienie o aktywacji. Wszystkie wykryte wycieki są wyświetlane jako powiadomienia — kliknięcie powiadomienia otwiera ekran ze szczegółowym raportem (LeakTrace).
Do personalizacji można stworzyć własny AppWatcherInstaller i nadpisać parametry: timeout oczekiwania na GC, listę śledzonych typów obiektów, włączenie zapisywania zrzutu sterty na dysk. Jednak dla 90% projektów konfiguracja domyślna jest optymalna.
Począwszy od wersji 2.12, LeakCanary obsługuje automatyczne śledzenie ViewModel, zakresów korutyn i obiektów State Compose. Dodatkowe zależności nie są wymagane — biblioteka sama określa, które komponenty Jetpack są używane w projekcie, i aktywuje odpowiednie detektory.
Raport LeakCanary (LeakTrace) — to wieloliniowy łańcuch referencji od GC Root do wyciekającego obiektu. Każda linia pokazuje klasę i pole, przez które przechodzi silna referencja. Programista powinien czytać łańcuch od dołu do góry: dolna linia — wyciekający obiekt, górna — punkt wejścia (GC Root).
Typowy LeakTrace wygląda tak: GC Root → statyczne pole Application → singleton → callback → Activity. Jeśli programista widzi taki łańcuch, problem jest jasny: singleton trzyma callback, który przechwycił referencję do Activity. Rozwiązanie — zastąpienie silnej referencji słabą w singletone.
┬
├─ 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)
╰
W tym przykładzie LeakCanary pokazuje, że MainActivity jest utrzymywane przez łańcuch: Application → ArrayList → MainActivity → CallbackWrapper → MyCallback → z powrotem MainActivity. Strzałka this$0 wskazuje, że anonimowa klasa MyCallback przechwyciła zewnętrzną referencję do Activity. Rozwiązanie — zrobienie callbacka słabą referencją lub anulowanie go w onDestroy.
LeakCanary pokazuje również stan wycieku dla każdego elementu łańcucha: NO (brak wycieku — to element główny), YES (obiekt powinien być zniszczony), UNKNOWN (nie udało się określić stanu). Status UNKNOWN nie oznacza problemu — to obiekt pośredni, którego LeakCanary nie może jednoznacznie sklasyfikować.
Przejście z wersji 1.x na 2.x było radykalne: programiści przepisali bibliotekę od nowa, zastępując przestarzały analizator HAHA własnym silnikiem Shark napisanym w Kotlinie. Shark działa znacznie szybciej, wymaga mniej pamięci do analizy i dokładniej określa główne przyczyny wycieków.
| Parametr | LeakCanary 1.x | LeakCanary 2.x |
|---|---|---|
| Język analizatora | Java (HAHA — fork Android SDK) | Kotlin (Shark — własny silnik) |
| Instalacja | Ręczna konfiguracja AppWatcher w Application | Automatyczna przez ContentProvider |
| Szybkość | 10–30 sekund na analizę zrzutu sterty | 1–5 sekund na analizę zrzutu sterty |
| Wydajność | Zajmuje 10–50 MB RAM podczas analizy | Zajmuje 2–10 MB RAM podczas analizy |
Kluczową zaletą Shark jest to, że nie ładuje całego zrzutu sterty do pamięci, ale przegląda jego graf referencji z minimalnymi alokacjami. To sprawia, że LeakCanary 2.x nadaje się do użytku na urządzeniach z niską ilością RAM bez ryzyka OutOfMemoryError podczas analizy.
W wersji 2.x pojawiła się również możliwość eksportu zrzutu sterty do pliku do późniejszej analizy w Android Studio Memory Profiler. Aby to zrobić, należy włączyć ustawienie dumpHeapWhenLeakFound w konfiguracji AppWatcher.
LeakCanary skutecznie wykrywa kilka klas wycieków charakterystycznych dla Androida. Najczęściej występuje wyciek przez statyczne referencje do Activity — programiści przechowują referencję do kontekstu Activity w singletone, a Activity nie może zostać zebrane przez GC po zakończeniu swojego cyklu życia.
Drugą pod względem częstotliwości kategorią są wycieki przez niezarejestrowane słuchacze. Jeśli w onStart został wywołany registerListener, ale w onStop/onDestroy nie wywołano unregisterListener — obiekt-słuchacz jest utrzymywany przez system nawet po zniszczeniu aktywności. LeakCanary jednoznacznie pokazuje, który słuchacz i w jakim serwisie systemowym pozostał aktywny.
// Typowy wyciek: Activity przechwycone w callbacku singletona
object AnalyticsManager {
private var callback: ((String) -> Unit)? = null
fun register(callback: (String) -> Unit) {
this.callback = callback // silna referencja na callback
}
fun unregister() {
callback = null // NIE ZAPOMNIJ wywołać w onDestroy!
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
AnalyticsManager.register { event ->
logEvent(event) // lambda przechwytuje this
}
// jeśli nie wywołasz unregister w onDestroy → wyciek Activity
}
}
Trzecią kategorią są wycieki przez Fragment w BackStack. Jeśli FragmentTransaction.addToBackStack() jest wywoływane bez usuwania Fragmentu przy powrocie, stare instancje Fragmentu pozostają w pamięci. LeakCanary pomaga wykryć takie ukryte wycieki na wczesnych etapach programowania.
Dla każdego wykrytego wycieku LeakCanary oferuje opis i zalecenia dotyczące naprawy. W wersji 2.14 dodano integrację z Android Lint — biblioteka może automatycznie tworzyć zadania w issue tracker po wykryciu wycieku w CI.
Często zadawane pytania
Tak, koniecznie. LeakCanary jest podłączane przez debugImplementation w build.gradle, co automatycznie wyklucza go z wydaniowej kompilacji. Jeśli podłączysz przez implementation, biblioteka trafi do wydaniowego APK i będzie pokazywać wycieki końcowym użytkownikom — to niedopuszczalne.
Wpływ na wydajność jest minimalny. LeakCanary aktywuje się tylko po onDestroy komponentu i nie ingeruje w renderowanie UI ani obsługę dotyku. Jedynym kosztem jest krótka pauza wymuszonego GC (około 100 ms) i zapis zrzutu sterty przy wycieku (setne części sekundy).
LeakCanary automatycznie zapisuje zrzut sterty w formacie HPROF w folderze aplikacji. Plik można wyeksportować przez Android Studio: Device File Explorer → data/data/com.example/files/leakcanary/. Aby go wyświetlić, otwórz plik w Memory Profiler przez Capture → Open Heap Dump.
Tak, od wersji 2.12 LeakCanary w pełni obsługuje Jetpack Compose. Biblioteka śledzi konteksty Composition i obiekty State, automatycznie wykrywając wycieki w funkcjach Composable. Osobna konfiguracja nie jest wymagana — działa od razu.
Fałszywe alarmy są możliwe, ale rzadkie. LeakCanary używa potrójnego wywołania GC przed ogłoszeniem wycieku, co eliminuje większość fałszywych pozytywów. Jeśli uważasz, że alarm jest fałszywy — utwórz IgnoredReference dla konkretnej klasy w konfiguracji.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również