Progress · 0/10 sections
⚙️ ⚙️ Performance & Memory
2 min read · Notion
How to analyze memory leaks in Android?
- LeakCanary: Auto-detects leaks in debug builds. Traces the reference chain.
- Android Profiler (Memory tab): Heap dumps, allocation tracking.
- Common causes:
- Holding Activity context in long-lived objects (use Application context instead).
- Not unregistering listeners/BroadcastReceivers.
- Static references to views.
- Anonymous inner classes holding outer Activity reference.
- Not clearing ViewBinding in
onDestroyView().
App startup performance
Three startup states:
- Cold start: Process not running. Most expensive. Optimize
Application.onCreate(). - Warm start: Process alive, Activity destroyed. Re-creates Activity.
- Hot start: Activity in background, brought to foreground.
Optimizations:
- Defer non-critical init with
App Startuplibrary. - Move heavy work off main thread.
- Use baseline profiles to pre-compile hot code paths.
- Reduce overdraw — flatten view hierarchy.
Detecting ANRs
ANR (Application Not Responding) occurs when main thread is blocked for >5 seconds (input) or >10 seconds (broadcast).
Prevention:
- Never do network/disk I/O on main thread.
- Use
Dispatchers.IOorDispatchers.Defaultfor heavy work. - Use
StrictModein debug to detect accidental main-thread I/O.
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectNetwork()
.penaltyLog()
.build()
)Compose performance pitfalls
- Lambda re-creation: Lambdas created inline in
items {}are unstable — hoist them. - Unstable classes: If a class has
varproperties or mutable collections, Compose can't skip it. Annotate with@Stableor@Immutable. - Too much state hoisting: Lifting state unnecessarily high causes more composables to recompose.
- Snapshot reads in wrong scope: Reading state in a composable that shouldn't care about it.
Use Compose Compiler Metrics (-Pcompose.metrics.enabled=true) to identify skippable vs unskippable composables.
Inline classes / value classes
@JvmInline
value class UserId(val id: Int)At runtime, UserId is represented as a plain Int — zero overhead wrapper. Useful for type-safe IDs, units, domain primitives without boxing penalty.