MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Memory leaks rarely crash on the first screen — they crash users three navigations deep. Here's the LeakCanary workflow I use to catch the common Android leak sources before they hit production.
On this page
A memory leak almost never announces itself. The app runs fine, then a user who keeps it open for an hour and bounces between screens hits an
OutOfMemoryErrorLeakCanary installs itself; there's no init code in
Applicationdependencies {
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
}It only runs in debug builds. After an
ActivityFragmentThe trace shows what's keeping your object alive. The bottom is the GC root; the top is the leaked object. The line that matters is the reference that shouldn't exist — usually something static or long-lived holding a short-lived object.
┬───
│ GC Root: Global variable in native code
│
├─ MySingleton.context ← holds an Activity. This is the leak.
│
╰→ MainActivity instance (leaking)When you see an
ActivityA Context held by a singleton. Passing an
Activity// Leak: Activity context stored in a singleton
class Analytics(private val context: Context)
Analytics(activity) // pins the Activity forever
// Fix: use application context
Analytics(activity.applicationContext)A coroutine outliving its screen. A job launched in the wrong scope keeps its captured references alive. Tie work to
viewModelScopeclass MyViewModel : ViewModel() {
fun load() = viewModelScope.launch { /* cancelled with the ViewModel */ }
}A listener you registered but never removed. Register in
onStartonStopAn inner-class handler. A non-static
HandlerActivityonDestroyLeakCanary tells you what leaked. When I want to see how much memory is growing over time, I open Android Studio's Memory Profiler, repeat the suspect navigation a dozen times, and force a GC. If the count of an
ActivityThe reason leaks reach production is that nobody navigates back and forth enough during normal testing. I treat any LeakCanary notification during development as a build-breaker, not a "later" item. Fixing one when the reference chain is fresh in front of you takes minutes; chasing an OOM report from the field takes a day.
The reason memory leaks survive testing is that they don't violate any obvious contract. The screen renders, the feature works, the tests pass. Nothing is wrong in the sense a crash is wrong — there's just an object that should have been collected and wasn't, quietly holding a few megabytes. One leaked Activity is harmless. The problem is that leaks accumulate: navigate into the leaking screen fifty times over a long session and you've pinned fifty view hierarchies, and now the garbage collector is thrashing and the next allocation throws. The symptom appears far from the cause, both in time and in screens, which is exactly why it's so hard to catch by hand.
This is why I value tools that make the invisible visible. A leak you can't see is a leak you'll argue doesn't exist until a user's crash report proves otherwise. LeakCanary's whole value is converting "the app feels slow after a while" — the vaguest bug report there is — into a precise reference chain you can act on in minutes. It shifts the work from reproducing a mystery to reading a stack trace, and reading a stack trace is something every Android developer already knows how to do.
The mindset shift that helped me most was treating retained memory as a first-class correctness property, not a performance nicety to look at someday. A destroyed Activity that stays in memory is a bug in the same category as a null pointer; it's just one that fails later and quieter. Once I started treating leak notifications as build-breakers instead of suggestions, the OOM reports from the field essentially stopped. The cost of fixing a leak while its reference chain is fresh on your screen is a few minutes. The cost of diagnosing one from an aggregated crash dashboard weeks later, with no reproduction steps, is most of a day. Catching it early isn't just cleaner — it's the cheaper path by a wide margin.
debugImplementationviewModelScopeSudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus