Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/AmolPardeshi99/android-performance-skills/llms.txt

Use this file to discover all available pages before exploring further.

Beyond LeakCanary, Android provides several complementary tools for diagnosing memory issues at different granularities: the Android Studio Memory Profiler for interactive investigation, adb shell dumpsys meminfo for quick field checks, adb shell am dumpheap for CI-automated heap dumps, Perfetto for production-grade profiling, and ApplicationExitInfo for post-mortem OOM detection.

Android Studio Memory Profiler

The Memory Profiler is the best tool for interactive investigation. It shows live memory allocation, lets you force a GC, and captures a heap dump that you can navigate in the IDE.
1

Run the app in Profile mode

In Android Studio, select Run → Profile (or click the profile icon). This launches the app with profiling agents attached.
2

Open the Memory tab

In the Profiler panel, click Memory to open the live memory timeline.
3

Navigate between screens multiple times

Open and close the screens you want to investigate. Repeated navigation amplifies leaks, making them easier to spot as a rising memory baseline.
4

Force a garbage collection

Click the GC bucket icon in the Memory Profiler toolbar to trigger a full GC. Legitimate objects will be collected; leaked objects will remain.
5

Capture a heap dump

Click Capture Heap Dump. Android Studio captures an HPROF snapshot of the current heap.
6

Filter for Activity and Fragment leaks

In the heap dump view, use Filter → Show Activity/Fragment leaks to surface destroyed instances that are still in memory.
7

Trace to the GC root

Click a suspicious instance → open the References pane → follow the chain upward to the GC root. The retaining reference is your leak.
What to look for: objects with a large Retained Size that should have been freed after navigation. If an Activity or Fragment instance appears in the dump after you have pressed Back, it is a confirmed leak.

adb shell dumpsys meminfo

dumpsys meminfo provides a fast snapshot of the memory state of your app — no IDE required. It is useful for quick checks on physical devices in the field.
adb shell dumpsys meminfo com.your.package.name

# Key fields to watch:
# Activities:  N  ← should drop to 1 after pressing Back on a screen
# Views:       N  ← high count after navigation = View leak
# AppContexts: N
# TOTAL: xx MB ← compare before/after repeated navigation to detect growth
The Activities count is the most actionable signal: after pressing Back to close a screen, the count should fall by one (and eventually stabilize at 1 for the root Activity). If it stays elevated, an Activity is being retained by something. A rising TOTAL across repeated navigation cycles — with GC in between — indicates an accumulating leak.

adb shell am dumpheap

am dumpheap produces an HPROF heap dump via the shell, with no IDE attachment needed. This makes it suitable for CI pipelines, automated test runs, and devices without USB debugging UI.
1

Find the PID of your app

adb shell pidof com.your.package.name
2

Dump the heap to device storage

adb shell am dumpheap <PID> /data/local/tmp/heap.hprof
3

Pull the HPROF to your machine

adb pull /data/local/tmp/heap.hprof ./heap.hprof
4

Convert to Android Studio-compatible format

The raw HPROF from the device must be converted before Android Studio can open it:
hprof-conv heap.hprof heap_converted.hprof
5

Open in Android Studio or analyse with LeakCanary CLI

Open via File → Open → heap_converted.hprof in Android Studio. Alternatively, analyse programmatically using the LeakCanary Shark library.
# For debuggable builds on API 30+, you can also trigger via shell:
adb shell cmd activity dumpheap com.your.package.name /data/local/tmp/heap2.hprof

Perfetto + heapprofd

Perfetto is the production-grade profiling platform for Android. heapprofd hooks into the allocator and samples Java and native heap allocations continuously — without recompiling the app — making it suitable for profiling on user builds with debuggable=true.
# Java heap sampling via Perfetto (no recompile; works on user builds with debuggable=true)
# Configure in Perfetto config:
# track_event { name_filter_type: MATCH_ANY name_filter: "com.your.package" }
# java_hprof_config { continuous_dump_config { dump_phase_ms: 0 dump_interval_ms: 10000 } }

# Open resulting trace at https://ui.perfetto.dev → Memory → Java Heap Graph
Open the resulting trace at ui.perfetto.dev and navigate to Memory → Java Heap Graph to explore object retention over time. Unlike a single heap dump, Perfetto captures allocation history, so you can see exactly when objects are allocated and whether they are ever freed.

ApplicationExitInfo — post-mortem OOM detection

ApplicationExitInfo (API 30+) records the reason for each process termination, including out-of-memory kills. Query it at startup to surface OOM events that happened in previous sessions — events that would otherwise be invisible.
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val am = getSystemService(ActivityManager::class.java)
            am.getHistoricalProcessExitReasons(packageName, 0, 5).forEach { info ->
                if (info.reason == ApplicationExitInfo.REASON_LOW_MEMORY) {
                    // Report to crash analytics; optionally parse HPROF
                    crashReporter.logOomExit(info.description, info.timestamp)
                }
            }
        }
    }
}
Pass the exit info’s description and timestamp to your crash reporting backend so you can correlate OOM kills with app version, device model, and session data.

Code review checklist

  • Every Fragment with ViewBinding nulls _binding in onDestroyView
  • No Activity, Fragment, View, or Binding stored in ViewModel, singleton, or companion object
  • All singletons and long-lived objects use applicationContext, not Activity context
  • Every listener/observer/receiver registration has a symmetric unregister in the paired lifecycle method
  • ConnectivityManager.CONNECTIVITY_ACTION not used (deprecated API 28+) — use NetworkCallback
  • LiveData observed with viewLifecycleOwner (not this) in Fragments
  • Flow collected inside repeatOnLifecycle(STARTED) block
  • No GlobalScope.launch in any production code
  • All Cursor, InputStream, OutputStream, MediaPlayer, DB connections use use {}
  • Handler postDelayed runnables have matching removeCallbacks in teardown
  • Custom Views release animators, bitmaps, and listeners in onDetachedFromWindow
  • Custom Views call TypedArray.recycle() (or use {}) in init {} — always in finally
  • registerForActivityResult called at Fragment/Activity construction time, not in onViewCreated
  • NavController.addOnDestinationChangedListener paired with removeOnDestinationChangedListener in onDestroyView
  • by lazy {} in Fragment does not capture this — uses applicationContext or deferred init pattern
  • Choreographer.postFrameCallback has matching removeFrameCallback in onPause/onDestroyView
  • ProcessLifecycleOwner observers use only application-scoped objects — never Activity/Fragment instances
  • Services call stopSelf() on task completion; WorkManager preferred over long-running services
  • WebView hosted in a separate process via android:process
  • LeakCanary in debug builds; zero leaked signatures suppressed without documented justification
  • Hilt / DI scope annotations match the actual lifetime of each dependency
  • LeakCanary 2.14 in debug builds with DetectLeaksAfterTestSuccess rule in UI tests
  • adb shell dumpsys meminfo Activity count checked after navigation — drops to 1 after Back
  • Heap dump captured via adb shell am dumpheap or Memory Profiler after repeated navigation
  • ApplicationExitInfo queried at startup to detect REASON_LOW_MEMORY exits in production

Build docs developers (and LLMs) love