Skip to content

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

  1. Open Profiler
  2. Start app
  3. Perform actions (rotate, navigate)
  4. Force GC
  5. Check Memory tab
  6. 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