What you’ll study
- What a task and its back stack are, and what a launch mode changes.
- The two routes every launch takes: stay in the caller’s task, or find a task by affinity.
- The four outcomes: reuse, create, clear the top, new task.
- The four modes, and the intent flags that stand in for them.
- How the system decides, step by step, in
ActivityStarter. - The traps: affinity, results, non-Activity contexts,
singleInstanceand Back.
How it connects
A launch mode only answers two questions: which task, and new or reused. The first is
decided by whether the launch ends up with FLAG_ACTIVITY_NEW_TASK; if it does, taskAffinity
picks the task. The second depends on the mode and on what’s already in that task. Every edge case
in this guide is one of those two answers coming out differently than you expected.
Read it at your level
| Level | Read | You should be able to |
|---|---|---|
| Student | The basics, the tree, the phone demo | Say what a task is and what each mode does, in one line each |
| Fresher | + the checklist and quick reference | Predict the back stack after any launch in the demos |
| Junior | + Why it helps, the first eight cross questions | Fix duplicate screens and “wrong Back” bugs; answer the common interview questions |
| Mid | + The low level, The map | Explain where the decision is made and why singleTask can stay in your own task |
| Senior | + Edge cases, the last five cross questions | Reason about results, non-Activity contexts and affinity across apps; debug with dumpsys |
The basics
Every screen you open goes into a task: a stack of Activities the user moves through with Back.
Recents shows one card per task. Most of the time an app has one task, and every startActivity()
pushes a new Activity on top of it.
Launch modes change that default. You set one on the Activity in the manifest
(android:launchMode), or ask for similar behaviour per call with intent flags:
<activity
android:name=".TargetActivity"
android:launchMode="singleTask"
android:taskAffinity="com.example.reader" />
Every startActivity(T) then answers two questions: which task does T open in, and does
Android create a new T or reuse one and call onNewIntent()? Launch modes,
taskAffinity and FLAG_ACTIVITY_NEW_TASK all feed into those two answers, which is why they are
easy to mix up when you learn them one by one.
The high level
Seen as a single decision tree, they stop being four special cases. There are only two routes: stay in the caller’s task, or go looking for a task by affinity.
startActivity(T)
│
└─ Does the launch ask for a task search?
(singleTask, singleInstance, or the intent has FLAG_ACTIVITY_NEW_TASK)
│
├─ NO ─► T joins the caller's task
│ └─ singleTop AND T already on top of the stack?
│ ├─ YES ─► REUSE: deliver onNewIntent()
│ └─ NO ─► CREATE: push a new T on the caller's stack
│
└─ YES ─► Is there a task with T's taskAffinity?
├─ NO ─► NEW TASK: create a task with T as its root
└─ YES ─► bring that task to the front, then by launch mode:
│
├─ singleInstance
│ └─► T lives alone: REUSE, deliver onNewIntent()
│
├─ singleTask: is T already in this task?
│ ├─ YES ─► CLEAR TOP: destroy everything above T,
│ │ REUSE, deliver onNewIntent()
│ └─ NO ─► CREATE: push a new T on this task
│
└─ standard / singleTop + FLAG_ACTIVITY_NEW_TASK
└─ singleTop AND T on top of this task?
├─ YES ─► REUSE: deliver onNewIntent()
└─ NO ─► CREATE: push a new T on this task
Every leaf ends in one of four outcomes: reuse the T you have, create a new T on an
existing stack, clear the top of a task down to T, or start a new task with T as its
root.
Watch the base cases on a phone
The same rules, played on a device. Each card is one Activity instance with its own number, so you can
see when Android reuses the one you have and when it makes another. Press Back after a launch:
where you land is the part people usually get wrong, especially with singleInstance.
The checklist to run in your head
Whenever you see a launch, run these three steps.
Step 1: Does it ask for a task search?
- No:
standardorsingleTopwithout the flag. Go to step 2: it stays in the caller’s task. - Yes:
singleTask,singleInstance, orFLAG_ACTIVITY_NEW_TASKon the intent. Go to step 3: nowtaskAffinitydecides.
Step 2: The caller’s task (standard and singleTop)
T stays in the same task as the screen that called it. Ask one thing: is it singleTop and
already at the very top of the stack?
- Yes: don’t create a new one. Call
onNewIntent(). - No (it’s not on top, or it’s
standard): push a brand-new instance on top of the stack.
Step 3: Affinity and task (singleTask and singleInstance)
- Find the matching task. Does a task with this Activity’s
taskAffinityexist?- No: create a new task with that affinity and create the Activity inside it.
- Yes: bring that task to the foreground.
- Look inside that task.
singleTask: does the Activity already exist in that task?- Yes: pop and destroy every Activity above it so it reaches the top, then call
onNewIntent(). - No: create a new instance and place it on top of that task.
- Yes: pop and destroy every Activity above it so it reaches the top, then call
singleInstance: the Activity is already alone in its own task. CallonNewIntent().
Quick reference
| Launch mode | Looks for a task? | Reuses an instance when | Others in its task? |
|---|---|---|---|
standard |
Only with the flag | Never | Yes |
singleTop |
Only with the flag | It’s on top of the stack | Yes |
singleTask |
Always | It’s anywhere in the task (clears above it) | Yes |
singleInstance |
Always | It exists at all | No, it’s alone |
Checkpoint
Before going deeper, you should be able to say:
- A launch either stays in the caller’s task or searches for a task by
taskAffinity. singleTask,singleInstanceandFLAG_ACTIVITY_NEW_TASKcause the search;standardandsingleTopdon’t.- Reuse means
onNewIntent(), notonCreate(). singleToponly looks at the top;singleTasklooks through the whole task and clears aboveT.
If any of these is fuzzy, play the two demos again before reading the code.
The low level
The tree above is a model. The real decision is made in the system server, in
ActivityStarter (package com.android.server.wm, AOSP frameworks/base). Following one launch
through it, these are the steps that map onto the tree:
1. Read the launch mode, and force a new task where required
setInitialState() reads the target’s launchMode from its ActivityInfo. In code the modes are
numbers: LAUNCH_MULTIPLE (0, which is standard), LAUNCH_SINGLE_TOP (1),
LAUNCH_SINGLE_TASK (2), LAUNCH_SINGLE_INSTANCE (3) and LAUNCH_SINGLE_INSTANCE_PER_TASK (4).
It adds FLAG_ACTIVITY_NEW_TASK straight away for singleInstancePerTask, and for an Activity whose
required display category differs from the caller’s. Then, if the intent asks for a new task
and a result, it cancels the result on the spot (more in the edge cases).
2. Decide whether to search for a task: computeLaunchingTaskFlags()
This is step 1 of the checklist, in code. FLAG_ACTIVITY_NEW_TASK is added when:
- there is no calling Activity (a
Service, a receiver, the applicationContext); the log says “startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK”; - the caller is
singleInstance, because nothing may join its task; - the target is
singleTaskorsingleInstance.
So “does it ask for a task search?” really means “does the launch end up with NEW_TASK?”
3. Find a task to reuse: resolveReusableTask()
Android only tries an existing task when the launch has NEW_TASK (without
FLAG_ACTIVITY_MULTIPLE_TASK) or the target is singleTask/singleInstance, and the caller is
not waiting for a result. Then:
singleInstancesearches for the Activity itself (findActivity): “there can be one and only one instance … always in its own unique task”, as the source puts it.- Everything else asks
RootWindowContainer.findTask()for the best task, matched bytaskAffinity. singleInstancePerTaskonly reuses a task whose root is that Activity, even if another task has the same affinity.
4. Reuse instead of create
- Top of the stack (
singleTop).deliverToCurrentTopIfNeeded()skips creatingTwhen the top running Activity is the same component, for the same user, still attached to a process, and the launch issingleTopor hasFLAG_ACTIVITY_SINGLE_TOP. - Clear the top (
singleTaskand friends).complyActivityFlags()callsTask.performClearTop()when the launch hasFLAG_ACTIVITY_CLEAR_TOPor the target issingleTask,singleInstanceorsingleInstancePerTask. Every Activity aboveTis finished, andTgets the new intent throughdeliverNewIntent(). - The
CLEAR_TOPcatch. InTask.clearTopActivities(), ifTitself isstandardand the launch has noFLAG_ACTIVITY_SINGLE_TOP,Tis finished too and created again. - Replace the task.
NEW_TASK | CLEAR_TASKempties the whole task first (performClearTaskForReuse), soTbecomes its new root.
5. Deliver
A reused Activity is paused if it was resumed, gets onNewIntent(intent), then resumes. A new
Activity goes through onCreate() as usual. Either way, the task that holds it is moved to the
front.
Why it helps
- “My
startActivityForResult()returnedRESULT_CANCELEDinstantly.” The launch was given a new task while a result was expected, so the system cancelled the result before the screen even opened. - “
singleTaskdidn’t create a separate task.” Same affinity as the caller. Setandroid:taskAffinity, or accept that the “task search” finds your own task. - “The screen showed old data after being reused.”
onCreate()didn’t run;onNewIntent()did. Handle the new intent there, and callsetIntent()if later code readsgetIntent(). - “Back went somewhere unexpected.” Each task has its own back stack. A
singleInstancescreen sits alone in its task, so Back from it returns to whichever task was in front before. - “Two copies of the same screen.”
standard(or asingleTopthat wasn’t on top). Decide whether you wantsingleTop,CLEAR_TOP | SINGLE_TOP, or a navigation-level fix.
The map
| Idea | Where it lives (AOSP / SDK) |
|---|---|
| The four modes in the manifest | android:launchMode, ActivityInfo.launchMode |
| Mode numbers | ActivityInfo.LAUNCH_MULTIPLE (standard), LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK, LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_INSTANCE_PER_TASK |
| Which task the app “belongs to” | android:taskAffinity, ActivityInfo.taskAffinity |
| One launch, end to end | ActivityStarter.startActivityInner() |
| Forcing a new task | ActivityStarter.setInitialState(), computeLaunchingTaskFlags() |
| Finding a task to reuse | ActivityStarter.resolveReusableTask(), RootWindowContainer.findTask() / findActivity() |
singleTop reuse |
ActivityStarter.deliverToCurrentTopIfNeeded() |
| Clear top, clear task | ActivityStarter.complyActivityFlags(), Task.performClearTop(), clearTopActivities() |
| The callback you get | Activity.onNewIntent(), setIntent() |
| Cancelled results | ActivityStarter.sendNewTaskResultRequestIfNeeded() |
| Non-Activity context check | ContextImpl.startActivity() |
| Seeing it on a device | adb shell dumpsys activity activities |
Edge cases
- Same affinity, same task. Every Activity’s default
taskAffinityis the app’s package name. SoFLAG_ACTIVITY_NEW_TASKorsingleTaskfrom inside your own app usually finds the caller’s own task, and nothing looks different. GiveTa differenttaskAffinityto actually see a second task. singleInstancelooks for itself, not an affinity. Its task only ever holds that one Activity, so the real question in step 3 is “isTalready running?”. If not, it gets a fresh task even when a task with the same affinity exists.- Anything started from a
singleInstanceActivity getsNEW_TASKadded, because it can’t join that task. - The flag can just bring a task forward. With
FLAG_ACTIVITY_NEW_TASK, if a task is already running for the Activity you are starting, Android brings that task to the front in its last state instead of starting a new instance. This is how launchers behave. - Results and new tasks don’t mix. If the intent carries
FLAG_ACTIVITY_NEW_TASKand you usedstartActivityForResult(), the result is cancelled immediately (“Activity is launching as a new task, so cancelling activity result.”). And a launch that expects a result never reuses an existing task. - Starting from a non-Activity context.
ContextImpl.startActivity()throws “Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag”, except for apps targeting Android 7.0 to 8.1 (API 24 to 27), where the check is skipped and the system addsNEW_TASKitself. - Flags can stand in for modes.
FLAG_ACTIVITY_SINGLE_TOPbehaves likesingleTop, andFLAG_ACTIVITY_CLEAR_TOPgives you the “destroy everything above it” step withoutsingleTask. - Android 12 added
singleInstancePerTask. The Activity is always the root of its task and other Activities can stack above it. UnlikesingleInstance, it can have several instances, each in its own task, when the intent setsFLAG_ACTIVITY_MULTIPLE_TASKorFLAG_ACTIVITY_NEW_DOCUMENT. singleTopneeds a live top. The reuse check requires the top Activity to be attached to a process and to belong to the same user; otherwise a new instance is created.
Cross questions
The questions that come up about launch modes, from screening calls to whiteboard rounds. Try each one before you open the answer.
T is standard. From B you call startActivity(T) twice. How many Ts are there?
Two. standard never reuses, not even when a T is already on top. The stack is A › B › T › T, and Back has to be pressed twice to leave.
T is singleTop and the stack is A › T › B. What does startActivity(T) do?
It creates a second T: A › T › B › T. singleTop only looks at the very top of the stack, and B is there.
T is singleTask and the stack is A › T › B › C. What happens, and which callbacks run?
B and C are destroyed, so the stack becomes A › T. T is not recreated: it gets onNewIntent() and then onResume(). getIntent() still returns the old intent unless you call setIntent(intent) in onNewIntent().
You made an Activity singleTask, but launching it from your own app never creates a new task. Why?
Every Activity's default taskAffinity is the package name, so the task Android finds is the one you are already in. Give the Activity its own taskAffinity if it should live in a separate task.
T is singleInstance. T starts a standard Activity D. Where does D go?
Not into T's task, because nothing else can join it. D opens as if the intent had FLAG_ACTIVITY_NEW_TASK: in a task matching D's affinity, usually the app's main task.
Why does startActivity() from a Service or the application Context fail without a flag?
A non-Activity context has no task to put the new Activity in. Android throws an AndroidRuntimeException unless the intent has FLAG_ACTIVITY_NEW_TASK, which tells it to find or create a task.
What is the difference between singleTask and FLAG_ACTIVITY_CLEAR_TOP on a standard Activity?
Both destroy the Activities above T. With CLEAR_TOP alone, a standard T is destroyed too and created again with the new intent. Add FLAG_ACTIVITY_SINGLE_TOP, or use singleTask, to keep the instance and get onNewIntent() instead.
A notification opens a detail screen. How do you make Back go to the app's home screen instead of closing the app?
Build a back stack for the notification's PendingIntent with TaskStackBuilder (or a Navigation deep link), so the task opens as Home › Detail rather than Detail alone.
Which code actually decides what a launch does?
ActivityStarter in the system server (com.android.server.wm). computeLaunchingTaskFlags() decides whether the launch needs a task search, resolveReusableTask() finds the task, and deliverToCurrentTopIfNeeded() and complyActivityFlags() decide between reuse, clear-top and create.
Your startActivityForResult() gets RESULT_CANCELED before the screen even shows. Why?
The intent has FLAG_ACTIVITY_NEW_TASK. A new task can't return a result to another task, so the system sends RESULT_CANCELED immediately and launches the Activity without a link back to you.
What does FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK do?
It finds (or creates) the target's task, removes every Activity in it, and starts T as the new root. It is the usual way to "log out": the old back stack can't be reached with Back any more.
Is standard a real constant?
In code it's ActivityInfo.LAUNCH_MULTIPLE (value 0), which is a good description: multiple instances are allowed. The manifest value is standard.
How do you check what a launch really did on a device?
Run adb shell dumpsys activity activities. It prints every task with its affinity, root intent and stack of Activities, so you can compare the device with the tree.
Revision: key points
- Every launch answers two questions: which task, and new or reused.
- A task search happens when the launch ends up with
NEW_TASK: targetsingleTask/singleInstance, callersingleInstance, no calling Activity, or the flag itself. - The task is found by
taskAffinity(default: the package name), except forsingleInstance, which looks for itself. standard(LAUNCH_MULTIPLE) always creates;singleTopreuses only the top;singleTaskclears everything aboveT;singleInstancelives alone.- Reuse calls
onNewIntent(); callsetIntent()if later code readsgetIntent(). CLEAR_TOPon astandardTrecreates it, unlessSINGLE_TOPis also set.NEW_TASK | CLEAR_TASKreplaces the whole task: the usual log-out flow.NEW_TASKandstartActivityForResult()don’t mix: the result is cancelled at once.- Check what really happened with
adb shell dumpsys activity activities.
Then close the page and try the cross questions from memory.
The fine print
The low-level walk-through follows ActivityStarter on AOSP main (October 2026). Method names
move between Android releases, and device makers can change the window manager, but the
decisions themselves have been stable for years. When something surprising happens,
adb shell dumpsys activity activities shows what the device actually did.
Part of the internals series: why read internals, WorkManager, ViewModel, process death and saved state.