Activities, tasks and the back stack
A task is a stack of activities the user thinks of as one thing. Launch modes and flags are the rules for putting activities into tasks — and every one of them is a trade-off about Back.
Open a mail app, tap a message, tap a link, and you have three screens in a stack. Press Back three
times and you are where you started. That stack is not a UI widget — it is a system-level data
structure that system_server owns, and the app only asks to push and pop entries.
TL;DR#
- A task is a stack of activities, usually experienced as one thing. The launcher starts one; each app can have several.
- Back pops the current task. When the last activity in a task finishes, the task is removed and the previous task becomes visible.
- Launch modes (
standard,singleTop,singleTask,singleInstance) decide whether a new instance is created, reused, or placed in a separate task. - flags are per-launch overrides:
NEW_TASK,CLEAR_TOP,CLEAR_TASK,SINGLE_TOP,NO_HISTORY. - The back stack is also the predictive back contract. Since Android 16 it is on by default for targeting apps, which means the system draws the destination before Back completes.1
Mental model#
Think of a stack of paper on a desk.
Each sheet is an . You can put a new sheet on top, take the top sheet off, or — with the right permission — reach down, pull out a sheet, and drop everything above it. The desk (a task) keeps its own pile. Several desks may be open in the room, but you are looking at one.
Where the analogy breaks#
The sheets are not static. An activity that is not on top may be destroyed while it sits in the pile, and when you take the sheet above it off, the system may rebuild it from a saved bundle. So the stack holds positions and saved state, not necessarily live objects. That is why "the activity below is still there" is a convenient lie and not a guarantee.
The big picture#
Text description of this diagram
An interactive task stack simulator. Five activities are available: MainActivity and DetailActivity in standard mode, SearchActivity in singleTop, InboxActivity in singleTask and PlayerActivity in singleInstance. Launch buttons push activities according to their launch mode, and toggles apply the flags FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_CLEAR_TOP and FLAG_ACTIVITY_CLEAR_TASK. The diagram draws each task as a horizontal row, with instances ordered left to right and the top of the stack marked. The event log explains each decision: a new instance pushed, an existing instance reused with onNewIntent, a task created, or activities cleared above an existing instance. A Back button pops the current task and removes it when it becomes empty.
Press Detail three times and watch three instances pile up. Then launch Inbox: a new task
appears, because singleTask roots its own. Then launch Inbox again and nothing new is created —
the existing task is brought forward and everything above the instance is cleared.
Deep dive#
A task is not an app#
The word "task" suggests "an app's windows", and that is usually true but not definitionally true. Activities from different apps can share a task (a share sheet is a task containing your activity and the target's), and one app can own several tasks.
ActivityTaskManagerService, inside system_server, owns the task list. Your app calls
startActivity(); the service decides which task the new activity joins, what happens to the
activities above it, and whether the process needs to be created.2
The four launch modes#
The mode lives in the manifest:
<activity
android:name=".SearchActivity"
android:launchMode="singleTop"
android:exported="false" />| Mode | Behaviour | When it is right |
|---|---|---|
standard | a new instance every time | the default, and usually correct |
singleTop | reuse the instance if it is already on top | search, notifications, a detail screen reachable repeatedly |
singleTask | one instance, rooted in its own task; starting it again clears above it | the entry point of a feature, a "home" for a section |
singleInstance | like singleTask, and no other activity may join its task | rare: a full-screen call UI or a launcher-like surface |
singleTop is the only mode that does not surprise people. The other three change task structure,
which means they change what Back does — and Back is a promise to the user.
// singleTop in action: a search result tap re-delivers to the same screen.
class SearchActivity : ComponentActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent) // replace the intent the activity reports
viewModel.onQuery(intent.getStringExtra(EXTRA_QUERY))
}
}Note setIntent. If you do not call it, getIntent() keeps returning the first intent, and a
singleTop screen appears to ignore every subsequent launch. It is the single most common bug with
this mode.
Flags: per-launch overrides#
Flags let a caller do something different without changing the manifest. They compose, and some of them only make sense together.
val intent = Intent(context, InboxActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_SINGLE_TOP
}
context.startActivity(intent)FLAG_ACTIVITY_NEW_TASK— find or create a task whose root matches, then deliver there.FLAG_ACTIVITY_CLEAR_TOP— if an instance exists in the task, remove everything above it. Combine withSINGLE_TOPto keep the instance and callonNewIntentinstead of recreating it.FLAG_ACTIVITY_CLEAR_TASK— clear the target task entirely; only meaningful withNEW_TASK.FLAG_ACTIVITY_NO_HISTORY— the activity does not remain in the stack after the user leaves it.
Back, and what it means now#
Historically, Back was handled by the activity: onBackPressed() was called, and you could override
it. That model could not support a gesture that shows the destination before you commit.
The modern API is OnBackPressedDispatcher:
class DetailActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onBackPressedDispatcher.addCallback(this) {
if (hasUnsavedChanges()) {
showDiscardDialog()
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
}
}
}
}With predictive back, the system animates the destination during the gesture. To participate, an
activity must declare android:enableOnBackInvokedCallback="true" (or the app-level equivalent), and
navigation must go through the dispatcher so the system can see the destination ahead of time. Since
Android 16, targeting apps have predictive back enabled by default rather than opting in.1
taskAffinity in practice#
taskAffinity defaults to the package name, which is why most apps have one task. Changing it separates
an activity into its own task when launched with NEW_TASK:
<activity
android:name=".PlayerActivity"
android:taskAffinity="com.example.notes.player"
android:launchMode="singleTask" />That combination is the standard recipe for a "now playing" screen that must not stack: its own task, one instance, brought forward when the user taps it again. It is also the recipe for a surprising Recents screen, because the user now sees two cards for one app — which is correct if the two things genuinely are separate tasks.
The rule: change taskAffinity only when the user's mental model has two separate things. Otherwise the
default is right.
Multi-window and multi-resume#
Since Android 10, two activities from different apps can be visible at once, and both can be
resumed. That breaks the old assumption that at most one activity is resumed in the system, and it
changes what "pause" means: an activity can be paused yet still fully visible.
A practical consequence: do not stop animations or release camera resources in onPause() alone.
Ask whether the activity is still visible, or use a lifecycle-aware API that knows about multi-resume.
Recents is a different list#
The overview screen (Recents) is not the back stack. It lists tasks, one card per task, and its
contents are governed by taskAffinity, documentLaunchMode and whether an activity opts out with
android:excludeFromRecents.
That distinction explains a common complaint: an app can have a deep back stack and still show a single
card in Recents, because Recents shows the task, not the activities inside it. Conversely, a browser
with documentLaunchMode="intoExisting" can show one card per tab.
<activity
android:name=".DocumentActivity"
android:documentLaunchMode="intoExisting"
android:excludeFromRecents="false" />The four values of documentLaunchMode are none, intoExisting, always and never. always is
what produces one Recents card per document, which is how a document editor models tabs.
Task affinity and documentLaunchMode#
Two more knobs exist and are worth knowing by name rather than by detail. taskAffinity names the
task an activity prefers when launched with NEW_TASK; it defaults to the package name. And
documentLaunchMode on an activity (with FLAG_ACTIVITY_NEW_DOCUMENT) gives each document its own
entry in Recents — that is how browsers and document editors keep tabs in the overview screen.
How it evolved#
The task model has been stable since API 1, but the control over it has moved steadily from the app toward the system.
| API | Version | What changed |
|---|---|---|
| 1 | 1.0 |
|
| 3 | 1.5 Cupcake |
|
| 9 | 2.3 Gingerbread |
|
| 11 | 3.0 Honeycomb |
|
| 12 | 3.1 Honeycomb |
|
| 16 | 4.1 Jelly Bean |
|
| 20 | 4.4W KitKat (Wear) |
|
| 21 | 5.0 Lollipop |
|
| 24 | 7.0 Nougat |
|
| 26 | 8.0 Oreo |
|
| 28 | 9 Pie |
|
| 29 | 10 |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 33 | 13 Tiramisu |
|
| 36 | 16 Baklava |
|
The pattern: early Android let an app do almost anything to the stack; later releases added multi-window, then predictive back, then defaults that make the system's model authoritative.
Under the hood#
- ActivityTaskManagerService.javathe service that owns tasks and starts activities
- Task.javaa task: an ordered list of activities with a root
- ActivityRecord.javaone activity instance as the system sees it
- OnBackInvokedDispatcher.javathe client-side predictive back contract
Curiosity corner#
- Back can leave your app entirely. Popping the last activity of the last task returns to the launcher. There is no "exit" callback, which is why apps should save state as they go rather than at the end.
singleInstanceis almost always the wrong answer. It exists for surfaces that genuinely must be alone, such as an incoming-call screen. Using it for a normal feature breaks sharing and Recents.- A task can outlive its process. The system remembers the stack and rebuilds activities from saved state after a kill.
Try it#
adb shell dumpsys activity activities | grep -E "Task|Hist"
adb shell am start -n com.example.notes/.InboxActivity --activity-clear-top
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.
- docsBehavior changes: Apps targeting Android 16 or higherdeveloper.android.comaccessed 2026-10-27↩
- docsالمهام وحزمة التطبيقات الخلفية | App architecturedeveloper.android.comaccessed 2026-10-27↩
- docsBehavior changes: Apps targeting Android 17developer.android.comaccessed 2026-10-27↩
- docs活動簡介 | App architecturedeveloper.android.comaccessed 2026-10-27↩
- docsIntents and intent filters | App architecturedeveloper.android.comaccessed 2026-10-27↩
- docsFeatures and APIs overview | Android 12developer.android.comaccessed 2026-10-27↩
- docsAdd predictive backdeveloper.android.comaccessed 2026-10-27↩