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.
The most common source of long-lived Activity leaks is storing an Activity instance — or any object that holds one — in a location that outlives the Activity’s lifecycle. Static fields, singleton objects, and non-static inner classes are GC roots or connected to GC roots, and any Activity reference they hold prevents garbage collection.
1.2 Context misuse — Activity context in long-lived objects
A static Activity reference is a process-lifetime leak. Static fields are GC roots: anything reachable from them survives until the process dies. An Activity carries its entire View tree, all loaded Bitmaps, and the Context chain — storing one in a singleton can retain 5–50 MB indefinitely.
Root cause: Activity is a Context. Any long-lived object storing an Activity reference prevents that Activity from being GC’d. The worst offenders are singletons and static fields, which are GC roots and survive the process lifetime.
// ❌ LEAK: singleton stores Activity context
object ImageLoader {
private var context: Context? = null
fun init(activity: Activity) { context = activity } // Activity is never GC'd
}
// ❌ LEAK: custom View stores Activity as a field
class BadView(context: Context) : View(context) {
private val hostActivity = context as Activity // strong ref; outlives View
}
// ✅ CORRECT: use applicationContext in singletons
object ImageLoader {
private lateinit var appContext: Context
fun init(ctx: Context) { appContext = ctx.applicationContext } // lives as long as app
}
// ✅ CORRECT: WeakReference when Activity access is genuinely needed in long-lived objects
class SmartView(context: Context) : View(context) {
private val weakActivity = WeakReference(context as? Activity)
fun doActivityWork() {
weakActivity.get()?.let { activity ->
if (!activity.isFinishing && !activity.isDestroyed) {
// safe to use
}
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
// release all resources
}
}
Context selection quick reference
| Object lifetime | Correct context |
|---|
| Singletons, Room, OkHttpClient, WorkManager | applicationContext |
View inflation, dialogs, startActivity | Activity context — only within lifecycle |
| System services tied to UI (LayoutInflater) | Activity context |
| System services hardware-related (LocationManager, AlarmManager, CameraManager) | applicationContext |
| RecyclerView Adapters | view.context — never store the Activity reference |
| Composables | LocalContext.current — never hoist into ViewModel or singleton |
1.3 Static fields and companion objects holding Views or Activities
Root cause: Static fields are GC roots. Any object stored in a static field lives for the process lifetime. A View holds a reference to its Context (usually an Activity) — a static View leaks the entire Activity plus the View tree.
// ❌ LEAK
class SplashActivity : AppCompatActivity() {
companion object {
var instance: SplashActivity? = null // GC root → Activity never GC'd
var logoView: ImageView? = null // View → Context → Activity
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
instance = this
logoView = binding.logo
}
}
// ✅ CORRECT: share state via ViewModel or application-scoped event bus; no static UI references
class SplashViewModel : ViewModel() {
val splashComplete = MutableStateFlow(false)
}
// ✅ If a static reference is truly unavoidable: WeakReference + null in onDestroy
class SplashActivity : AppCompatActivity() {
companion object {
private var weakRef: WeakReference<SplashActivity>? = null
fun instance(): SplashActivity? = weakRef?.get()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
weakRef = WeakReference(this)
}
override fun onDestroy() { super.onDestroy(); weakRef = null }
}
1.4 Non-static inner classes and anonymous classes
Root cause: In Kotlin and Java, a non-static inner class or an anonymous class defined inside an Activity or Fragment captures an implicit this reference to the outer class. If the inner class is passed to a long-lived object (such as a singleton event bus), the outer class is retained indefinitely.
// ❌ LEAK: anonymous object captures 'this' (Activity) → EventBus (singleton) holds it forever
class OrderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
EventBus.subscribe(object : EventListener {
override fun onOrderUpdate(event: OrderEvent) {
updateUI(event) // captures OrderActivity implicitly
}
})
// No matching unsubscribe → Activity leaks for app lifetime
}
}
// ✅ CORRECT: store listener reference; unsubscribe in lifecycle teardown
class OrderActivity : AppCompatActivity() {
private val orderListener = EventListener { event -> updateUI(event) }
override fun onStart() { super.onStart(); EventBus.subscribe(orderListener) }
override fun onStop() { super.onStop(); EventBus.unsubscribe(orderListener) }
}
// ✅ CORRECT (Java static inner class pattern to avoid implicit reference)
class MyActivity extends AppCompatActivity {
// Non-static: captures outer class → potential leak
// class BadRunnable implements Runnable { void run() { doUiWork(); } }
// Static: no implicit outer reference
static class SafeRunnable implements Runnable {
private final WeakReference<MyActivity> ref;
SafeRunnable(MyActivity a) { ref = new WeakReference<>(a); }
@Override public void run() {
MyActivity a = ref.get();
if (a != null && !a.isFinishing()) a.doUiWork();
}
}
}