Zygote
Components & Intentsintermediate

Context and Application

Context is the handle to everything the system provides — and the most over-used type in Android. Knowing which Context you hold is how you stop leaking activities.

11 minAPI 1–37 · 17interactivequizverified 2026-10-27

Context is the type every Android developer touches first and understands last.1 It looks like a grab-bag of helpers; it is actually a handle to a specific scope of system authority, and which one you hold determines what you are allowed to do and what you leak.

TL;DR#

  • A Context is a handle to the app's environment: resources, files, databases, preferences and the system services reachable through .
  • There are several kinds — Application, , , and wrapped contexts like ContextWrapper and ContextThemeWrapper. They differ in lifetime and in what they can start.
  • Application context is a singleton for the process. Activity context is not, and holding it in a long-lived object leaks the activity's entire view hierarchy.
  • Application is not a place for mutable global state.2 It is created before any component and lives as long as the process, which is not the same as "as long as the user needs it".
  • Use applicationContext for anything with a lifetime longer than a screen; use the activity context for anything that needs a window or a theme.

Mental model#

Think of Context as a building pass.

An activity's pass opens that one room and the shared facilities. A service's pass opens the utility corridors. The application pass opens the shared facilities but no individual room. All of them let you read the building directory (getSystemService), use the loading dock (files), and check the rules (resources).

Where the analogy breaks#

A pass is not just permission — it also carries a theme and a resource configuration. That is why ContextThemeWrapper exists, and why inflating a layout with the wrong context silently produces the wrong colours. Two contexts with identical permissions can still render differently, which a security analogy does not capture.

The big picture#

Loading diagram…
Text description of this diagram

A layered diagram of the app's runtime environment relevant to Context. At the bottom, the process holds one Application instance and one Resources/AssetManager pair. Above it, components — activities, services, providers, receivers — each hold a ContextWrapper whose base is the Application context but whose configuration and theme may differ. At the top, UI objects such as views, adapters and dialogs hold a reference to the activity context that created them. The diagram shows why a long-lived object holding an activity context keeps the whole upper layer alive, while an application context reference does not.

Deep dive#

What a Context actually is#

abstract class Context {
    abstract fun getAssets(): AssetManager
    abstract fun getResources(): Resources
    abstract fun getPackageName(): String
    abstract fun getSystemService(name: String): Any?
    abstract fun openFileInput(name: String): FileInputStream
    abstract fun getSharedPreferences(name: String, mode: Int): SharedPreferences
    // … and a few dozen more
}

The concrete classes split into two families:

ClassLifetimeHas a theme?Can start an activity without a flag?
Applicationthe processnono
Activitythe screenyesyes
Servicethe componentnono
ContextWrapperwhatever it wrapsinheritsinherits
ContextThemeWrapperwhatever it wrapsyesinherits

The "can start an activity" column is the one that produces the most confusing runtime exception in Android: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. It is not a bug; it is the system saying that a context with no task cannot decide which task the new screen belongs in.

The leak#

// A singleton that outlives every activity.
object Analytics {
    private var context: Context? = null
 
    fun init(ctx: Context) {
        context = ctx          // if ctx is an Activity, this leaks it
    }
}

An Activity holds its window, its view hierarchy, its drawables and its lifecycle observers. Keeping a reference to it in a process-lifetime singleton keeps all of that alive. Ten rotations later, ten activities are unreachable but not collected.

The fix is one word:

object Analytics {
    private lateinit var appContext: Context
 
    fun init(ctx: Context) {
        appContext = ctx.applicationContext
    }
}

What Application is for#

Application is created once per process, before any component. It is a reasonable place for:

  • Process-wide initialisation that must happen before any component runs.
  • Registering lifecycle callbacks for the whole app (registerActivityLifecycleCallbacks).
  • Holding a dependency-injection container, if you are not using Hilt.

It is a poor place for:

  • Mutable global state. A process can be killed and restarted while the user is "in" your app; globals vanish. Use ViewModel or persisted storage.
  • Anything that assumes a single instance per user. On a device with a work profile, your app runs in two processes with two Application objects.
  • Slow work. Every millisecond in Application.onCreate is added to cold start.
class NotesApp : Application() {
 
    lateinit var container: AppContainer
        private set
 
    override fun onCreate() {
        super.onCreate()
        container = AppContainer(this)
        // Keep this short: it is on the cold-start critical path.
    }
}

Which Context for which job#

A short decision table, because this is the question that produces the leaks:

NeedContext
open a database, file or DataStoreapplication
get a system service for app-wide useapplication
inflate a layout with the app themeactivity (or a themed wrapper)
show a dialog or a snackbaractivity
start an activity from a non-activityapplication plus FLAG_ACTIVITY_NEW_TASK
register a receiver tied to a screenactivity, unregistered with the screen
build a notificationapplication

The rows that say "activity" are the ones where the context carries a window or a theme. Everything else should default to the application context, because it cannot leak a screen.

Configuration and createConfigurationContext#

A context carries a Configuration: locale, density, orientation, UI mode. That is why the same layout resource resolves differently on the same device after a locale change, and why an application context captured once can be stale:

val config = Configuration(context.resources.configuration).apply {
    setLocale(Locale.forLanguageTag("fr"))
}
val localised = context.createConfigurationContext(config)
val text = localised.resources.getString(R.string.greeting)

Android 13 made per-app language a first-class setting, which means the system can change your app's locale without restarting the process. Code that cached a Resources object from startup will keep serving the old language.

ContextThemeWrapper, and why themes are confusing#

A ContextThemeWrapper overlays a theme on another context. Activity is one — it wraps the base context and applies android:theme from the manifest. That is why:

  • LayoutInflater.from(activity) produces themed views;
  • LayoutInflater.from(applicationContext) often does not;
  • a Dialog built with the application context throws WindowManager.BadTokenException.

When you need a themed context outside an activity — a Compose ContextThemeWrapper around a preview, for instance — wrap the application context explicitly rather than reusing an activity.

getSystemService and the manager objects#

Every manager you get from Context is a client-side proxy to a service in system_server:

val connectivity = context.getSystemService(ConnectivityManager::class.java)
val notification = context.getSystemService(NotificationManager::class.java)
val activity = context.getSystemService(ActivityManager::class.java)

The mapping from string constant to manager class lives in SystemServiceRegistry, a static table populated at class-load time. Which context you use matters: some services return a context-scoped object (for example, a LayoutInflater or a WindowManager bound to a display), and the application context may not have the display you expect in multi-display or desktop-windowing scenarios.

Context in Compose#

Compose replaces most Context usage with composition locals, but the Context is still there:

@Composable
fun rememberNetworkStatus(): Boolean {
    val context = LocalContext.current
    var online by remember { mutableStateOf(true) }
 
    DisposableEffect(context) {
        val manager = context.getSystemService(ConnectivityManager::class.java)
        val callback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) { online = true }
            override fun onLost(network: Network) { online = false }
        }
        manager.registerDefaultNetworkCallback(callback)
        onDispose { manager.unregisterNetworkCallback(callback) }
    }
    return online
}

LocalContext.current inside an activity's composition is the activity, not the application. That is usually what you want — it carries the right configuration and display — but it also means storing it in a remember that outlives the composition would leak. DisposableEffect is the guard.

The better rule in Compose: pass the values you need (a repository, a dispatcher, a formatter) down through parameters or composition locals provided at the top of the tree, rather than passing Context around. A composable that takes a Context is usually a composable that is hard to preview and hard to test.

Testing implications#

Because Context is a concrete class with a lot of surface, code that depends on it directly is hard to test. The usual answer is to depend on a narrower interface you own:

interface Clock { fun now(): Instant }
interface FileStore { fun read(name: String): ByteArray? }
 
class NotesRepository(
    private val store: FileStore,
    private val clock: Clock,
) { /* … */ }

Then the Android implementation adapts Context once, at the edge of the app, and everything inside is testable on the JVM. That is the same argument as dependency injection, applied to the platform's most pervasive type.

How it evolved#

Architecture changes29 releases
APIVersionWhat changed
11.0
  • Initial AOSP platform: Linux kernel, Dalvik VM, core libraries
  • The first zygote-based app spawning model
41.6 Donut
  • Support for different screen densities via resource qualifiers
52.0 Eclair
  • First multi-core-friendly scheduling and graphics work
82.2 Froyo
  • Trace-based JIT compiler added to Dalvik
  • Support for x86 and ARMv7 optimisations
92.3 Gingerbread
  • Concurrent garbage collector added to Dalvik (GC pause reduction)
113.0 Honeycomb
  • Hardware acceleration for the View system (opt-in per app)
133.2 Honeycomb
  • New screen-size resource qualifiers for compatibility scaling
144.0 Ice Cream Sandwich
  • One system image for phones and tablets
  • Hardware acceleration on by default
164.1 Jelly Bean
  • Project Butter: vsync, triple buffering, Choreographer
  • RenderThread architecture begins
174.2 Jelly Bean
  • Multi-user support on tablets
  • SELinux enforcement begins in the kernel (behind the scenes)
184.3 Jelly Bean
  • SELinux enforcement rolled out more broadly
  • Binder changes for restricted profiles
194.4 KitKat
  • Android Runtime (ART) appears as an experimental runtime
  • Kernel moved under a tighter vendor control
204.4W KitKat (Wear)
  • First time the platform shipped for a new form factor
215.0 Lollipop
  • ART becomes the only runtime: ahead-of-time compilation at install
  • 64-bit ARM and x86 support
  • RenderThread and OpenGL ES 3.1
  • Project Volta and JobScheduler
  • SELinux fully enforcing
225.1 Lollipop
  • Device protection ties verified boot to Google accounts
236.0 Marshmallow
  • Runtime permission model changes how every app asks for access
  • Doze batches background work
  • Fingerprint HAL
  • Adoptable storage
247.0 Nougat
  • Project Treble groundwork
  • A/B seamless updates
  • Hybrid JIT + AOT compilation in ART
  • MediaServer split into sandboxed processes
268.0 Oreo
  • Project Treble: the vendor interface separates vendor and system code
  • Background execution limits
  • Broadcast and service restrictions
  • HIDL for HALs
278.1 Oreo
  • Android Go: a lighter configuration of the same platform
289 Pie
  • App Standby Buckets
  • Mainline groundwork
  • Non-SDK interface restrictions (greylists) begin
  • BiometricPrompt
2910
  • Project Mainline: APEX and updatable system modules
  • Scoped storage
  • Bubbles
  • Project Mainline starts shipping through Play
3011
  • GKI: the Generic Kernel Image splits the kernel into core + vendor modules
  • App hibernation
  • Package visibility filtering
  • Incremental app installs
3112
  • Bluetooth permissions split into the nearby-devices family
  • Exact alarm permissions
  • Foreground service launch restrictions
3212L
  • Window size classes and activity embedding
3313 Tiramisu
  • Granular media permissions replace READ_EXTERNAL_STORAGE
  • The notification permission becomes a runtime permission
  • Predictive back gesture
3414 Upside Down Cake
  • Foreground service types are mandatory
  • Selected photos access via READ_MEDIA_VISUAL_USER_SELECTED
3515 Vanilla Ice Cream
  • 16 KB page size support for native libraries
  • Edge-to-edge is enforced when targeting 35
3616 Baklava
  • The release moves to Q2 (June) with a Q4 feature drop
  • Two SDK releases per year starting with 36
  • Desktop windowing arrives in developer options then stable
3717 Cinnamon Bun
  • Strict per-device app memory limits are enforced by the system
  • Generational garbage collection is added to ART's Concurrent Mark-Compact collector
  • A lock-free MessageQueue for apps targeting API 37
  • Compose-first: new platform and Jetpack APIs target Compose only; View libraries enter maintenance mode
  • Hybrid APK signing (v3.2) combines classical and ML-DSA post-quantum signatures
  • Native library dynamic code loading must be read-only

Context has been stable for a long time. The change is in how many contexts an app may have at once: multi-user, work profiles, multi-display and now desktop windowing all mean "the app" is no longer a single context with a single configuration.

Under the hood#

Curiosity corner#

  • getApplicationContext() can return a wrapper, not the Application. On some devices and in some test setups it is a ContextImpl wrapped around the application. Do not compare it by identity.
  • A Service is a Context. That is why a service can read resources and open files without reaching for the application.
  • The Application object is created once per process, not once per app. With a work profile, or with android:process, you get several.

Try it#

See the processes your app runs in

adb shell ps -A | grep com.example.notes

Inspect a running app's memory and context

adb shell dumpsys activity processes | grep -A 6 com.example.notes

Check yourself#

Check yourself

1 / 12

You call `startActivity()` for an activity in another app. Which check does the system perform before delivering the intent?

0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsContext | API referencedeveloper.android.comaccessed 2026-10-27↩
  2. docsProcesses and app lifecycledeveloper.android.comaccessed 2026-10-27↩
  3. docsBehavior changes: Apps targeting Android 16 or higherdeveloper.android.comaccessed 2026-10-27↩
  4. docsComponentsdeveloper.android.comaccessed 2026-10-27↩
  5. docsApp manifest overviewdeveloper.android.comaccessed 2026-10-27↩