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.
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
Contextis 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
ContextWrapperandContextThemeWrapper. 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.
Applicationis 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
applicationContextfor 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#
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:
| Class | Lifetime | Has a theme? | Can start an activity without a flag? |
|---|---|---|---|
Application | the process | no | no |
Activity | the screen | yes | yes |
Service | the component | no | no |
ContextWrapper | whatever it wraps | inherits | inherits |
ContextThemeWrapper | whatever it wraps | yes | inherits |
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
ViewModelor 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
Applicationobjects. - Slow work. Every millisecond in
Application.onCreateis 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:
| Need | Context |
|---|---|
| open a database, file or DataStore | application |
| get a system service for app-wide use | application |
| inflate a layout with the app theme | activity (or a themed wrapper) |
| show a dialog or a snackbar | activity |
| start an activity from a non-activity | application plus FLAG_ACTIVITY_NEW_TASK |
| register a receiver tied to a screen | activity, unregistered with the screen |
| build a notification | application |
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
Dialogbuilt with the application context throwsWindowManager.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#
| API | Version | What changed |
|---|---|---|
| 1 | 1.0 |
|
| 4 | 1.6 Donut |
|
| 5 | 2.0 Eclair |
|
| 8 | 2.2 Froyo |
|
| 9 | 2.3 Gingerbread |
|
| 11 | 3.0 Honeycomb |
|
| 13 | 3.2 Honeycomb |
|
| 14 | 4.0 Ice Cream Sandwich |
|
| 16 | 4.1 Jelly Bean |
|
| 17 | 4.2 Jelly Bean |
|
| 18 | 4.3 Jelly Bean |
|
| 19 | 4.4 KitKat |
|
| 20 | 4.4W KitKat (Wear) |
|
| 21 | 5.0 Lollipop |
|
| 22 | 5.1 Lollipop |
|
| 23 | 6.0 Marshmallow |
|
| 24 | 7.0 Nougat |
|
| 26 | 8.0 Oreo |
|
| 27 | 8.1 Oreo |
|
| 28 | 9 Pie |
|
| 29 | 10 |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 32 | 12L |
|
| 33 | 13 Tiramisu |
|
| 34 | 14 Upside Down Cake |
|
| 35 | 15 Vanilla Ice Cream |
|
| 36 | 16 Baklava |
|
| 37 | 17 Cinnamon Bun |
|
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#
- Context.javathe abstract base class and its API surface
- ContextWrapper.javathe delegation base used by activities, services and receivers
- SystemServiceRegistry.javathe string-to-manager table behind getSystemService
- Application.javathe process-wide context and its lifecycle callbacks
Curiosity corner#
getApplicationContext()can return a wrapper, not the Application. On some devices and in some test setups it is aContextImplwrapped around the application. Do not compare it by identity.- A
Serviceis 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#
adb shell ps -A | grep com.example.notes
adb shell dumpsys activity processes | grep -A 6 com.example.notes
Check yourself#
Check yourself
You call `startActivity()` for an activity in another app. Which check does the system perform before delivering the intent?
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.
- docsContext | API referencedeveloper.android.comaccessed 2026-10-27↩
- docsProcesses and app lifecycledeveloper.android.comaccessed 2026-10-27↩
- docsBehavior changes: Apps targeting Android 16 or higherdeveloper.android.comaccessed 2026-10-27↩
- docsComponentsdeveloper.android.comaccessed 2026-10-27↩
- docsApp manifest overviewdeveloper.android.comaccessed 2026-10-27↩