Context
Context Deep Dive¶
Overview¶
Context is an abstract class that provides access to system resources and services. It's your bridge to the Android framework. Understanding Context's types and lifecycle is crucial for avoiding memory leaks and using Android APIs correctly.
Context Architecture¶
What Context Provides¶
Context is essentially:
โโ Access to system services (LocationManager, SensorManager, etc)
โโ Access to app resources (strings, drawables, colors)
โโ File I/O operations
โโ Starting Activities, Services, Broadcasting
โโ Loading preferences and databases
โโ Creating views and inflating layouts
โโ Access to application state
Context Hierarchy¶
Context (abstract)
โโ ContextImpl (actual implementation)
โ โโ Activity Context (MainActivity:this)
โ โ โโ Can show dialogs
โ โ โโ Can start activities
โ โ โโ Tied to Activity lifecycle
โ โ
โ โโ Service Context (Service:this)
โ โ โโ No UI operations
โ โ โโ Tied to Service lifecycle
โ โ
โ โโ Broadcast Receiver Context
โ โโ Temporary
โ โโ Valid only during onReceive()
โ
โโ Application Context (getApplicationContext())
โโ Singleton, app-wide scope
โโ Lives entire app lifetime
โโ Cannot show UI operations
Context Types¶
Application Context¶
val appContext = context.applicationContext
// OR
val appContext = application // In Activity/Service
Characteristics: - Singleton (one per app) - Lives entire app lifetime - Cannot show UI dialogs/toasts - Safe for long-lived objects
Use for: - Global singletons - Database operations - SharedPreferences - Any non-UI work
// โ
CORRECT
val db = Room.databaseBuilder(applicationContext, MyDatabase::class.java, "db").build()
val prefs = applicationContext.getSharedPreferences("prefs", 0)
val singleton = object : MyService {
val context = applicationContext // Safe - won't leak
}
Activity Context¶
// In Activity
val activityContext = this
// Or explicitly
val activityContext: Context = this
Characteristics: - Tied to Activity lifecycle - Multiple per app - Can show UI operations (dialogs, toasts) - Die when Activity destroyed
Use for: - Showing dialogs, toasts in that Activity - UI operations tied to Activity - LayoutInflater creation - Can pass to short-lived objects only
// โ
CORRECT - Activity context for UI
AlertDialog.Builder(this)
.setTitle("Confirm")
.setPositiveButton("OK") { _, _ -> }
.show()
// โ
CORRECT - Application context for database
val db = Room.databaseBuilder(applicationContext, DB::class.java, "db").build()
Service Context¶
// In Service
val serviceContext = this
Characteristics: - Tied to Service lifecycle - No UI operations possible - Can request notifications (foreground services)
Use for: - Non-UI background work - Accessing services - Starting intents
Broadcast Receiver Context¶
// In onReceive()
fun onReceive(context: Context, intent: Intent) {
// context valid ONLY during onReceive()
// Do NOT store reference to it
}
Important: Context is temporary, don't store!
Context Memory Leaks¶
How They Happen¶
Leak Chain:
Long-lived Object โ holds Activity Context
โ Activity can't be garbage collected
โ Activity's views can't be GC'd
โ All resources tied to Activity leak
Common Leak Patterns¶
Pattern 1: Static Reference to Activity
companion object {
var activity: Activity? = null // โ MEMORY LEAK
}
// Usage
MyService.activity = this
// Result: Activity in back stack can't be destroyed
Fix: Use application context
companion object {
var context: Context? = null // โ
OK if using app context
}
// Usage
MyService.context = applicationContext
Pattern 2: Inner Class Holding Activity
class MyActivity : AppCompatActivity() {
inner class MyThread : Thread() { // โ LEAK
override fun run() {
Thread.sleep(60000)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MyThread().start() // Activity held for 60s
}
}
Fix: Static inner class with WeakReference
class MyActivity : AppCompatActivity() {
private static class MyThread(activity: MyActivity) : Thread() {
private val activityRef = WeakReference(activity)
override fun run() {
val activity = activityRef.get()
if (activity != null) {
// Use activity
}
}
}
}
Pattern 3: Unregistered Listeners
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
eventBus.register(this) // โ If never unregistered
}
// Activity destroyed but still registered
// EventBus holds reference โ leak
Fix: Unregister in onDestroy()
override fun onDestroy() {
eventBus.unregister(this)
super.onDestroy()
}
Pattern 4: Handler with Delayed Messages
private val handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handler.postDelayed({
// If this runs after Activity destroyed: leak
updateUI()
}, 60000)
}
Fix: Remove messages or use coroutines
override fun onDestroy() {
handler.removeCallbacksAndMessages(null) // Remove pending
super.onDestroy()
}
// Better: Use coroutines
viewModel.doLongOperation() // Auto-cancels with Activity
Application vs Activity Context: Decision Matrix¶
| Operation | App Context | Activity Context |
|---|---|---|
| Start Activity | โ Needs FLAG_ACTIVITY_NEW_TASK | โ |
| Start Service | โ | โ |
| Send Broadcast | โ | โ |
| Show Dialog | โ Crashes | โ |
| Show Toast | โ Often fails | โ |
| Get SystemService | โ | โ |
| Create Database | โ | โ |
| Get SharedPreferences | โ | โ |
| Inflate Layout | โ Sometimes fails | โ |
| LayoutInflater.from() | โ | โ |
| Store in Singleton | โ (only app) | โ Leak risk |
| Store in Static | โ (only app) | โ Leak risk |
| Pass to Thread | โ | โ Leak risk |
Context Lifetime Matching¶
Golden Rule¶
SAFE: Context Lifetime >= Code Lifetime
Unsafe:
Context [---Activity---]
Code [--------Thread---------]
โ Context dies, code still running
Safe:
Context [----App Lifetime----]
Code [---Activity-time-]
โ
Context lives longer
Example: Global Listener¶
// โ WRONG
class GlobalListener(val context: Activity) {
fun listen() {
// context dies when Activity destroyed
// Code still tries to use it
}
}
// โ
CORRECT
class GlobalListener(val context: Context) {
fun listen() {
if (context is Application) {
// Safe - app context
}
}
}
// Usage
GlobalListener(applicationContext) // Safe
GlobalListener(this) // Depending on usage, might leak
Best Practices¶
1. Prefer Application Context by Default¶
// Start with app context
val context = applicationContext
// Only use Activity context if UI operation required
if (needsDialog) {
// OK to use Activity context for short operation
}
2. Never Store Activity Context in Singleton¶
// โ WRONG
object MySingleton {
var activity: Activity? = null
}
// โ
CORRECT
object MySingleton {
var context: Context? = null
fun setContext(context: Context) {
this.context = context.applicationContext
}
}
3. Use WeakReference for Long-Lived References¶
class MyService(activity: Activity) {
private val activityRef = WeakReference(activity)
fun doWork() {
val activity = activityRef.get()
if (activity != null) {
// Use activity if still alive
}
}
}
4. Unregister All Listeners¶
override fun onDestroy() {
// Unregister ALL listeners
unregisterReceiver(myReceiver)
eventBus.unregister(this)
sensorManager.unregisterListener(mySensorListener)
super.onDestroy()
}
5. Remove Pending Handler Messages¶
override fun onDestroy() {
handler.removeCallbacksAndMessages(null)
super.onDestroy()
}
6. Use ViewModel Instead of Storing Context¶
// โ
BETTER
class MyViewModel : ViewModel() {
val data = MutableLiveData<String>()
}
// โ OLD WAY
class MyService(val activity: Activity) {
// Activity reference can leak
}
Detecting Context Leaks¶
LeakCanary¶
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
Usage: - Automatically detects Activity leaks - Shows leak chain in logcat - One of the best tools for this
Android Studio Profiler¶
- Open Profiler
- Start app
- Perform actions (rotate, navigate)
- Force GC
- Check Memory tab
- Look for retained objects
Manual Detection¶
// After activity destroyed, check logcat
getContext().getString(R.string.app_name) // If this works: leak
// Activity should be garbage collected by now
Key Takeaways¶
โ Application Context = app-wide, safe for singletons
โ Activity Context = tied to Activity, can show UI
โ Never store Activity Context in long-lived objects
โ Use WeakReference if you must hold Activity reference
โ Always unregister listeners in onDestroy()
โ Match Context lifetime to code lifetime
โ Prefer app context unless UI operation required
โ Use LeakCanary to detect leaks automatically