Tasks and the Back Stack

Ian Lake
Ian Lake
May 12, 2016 · 5 min read

What actually happens when you tap the launcher icon for your app? If you said ‘my app launches’, you are technically correct, the best kind of correct. To dive in a bit deeper, it is helpful to understand what a task is and how it interacts with a little thing called the back button.

Tasks

A task is a collection of metadata and information around a stack of activities (you can see exactly what kind of data by looking at the RecentTaskInfo class).

So when you tap the launcher icon for your app, what the system is actually doing is looking for a previously existing task (determined by the Intent and Activity it points to) to resume — getting you back to exactly where you were. If no existing task is found, then a new task is created with your newly launched activity as the base activity on the task’s back stack.

Back Stack

As you might imagine, a task’s back stack is tied together with the back button, but it goes both ways. When you start a new activity using startActivity(), that is (by default) pushing a new activity onto your task, causing the previous Activity to be paused (and stopped if the new activity fully obscures the previous activity).

A simple back stack

The back button (by default) then ‘pops’ the stack, calling finish() on the topmost activity, destroying it and removing it from the back stack and taking you back to the previous activity. This repeats until there’s nothing left in the back stack and you’re back at the launcher.

The Back Stack and Fragments

The back stack doesn’t apply only to activities: it also applies to fragments. When you provide a fragment transaction to add, replace, or remove a fragment from your UI, you can use addToBackStack() to effectively add the FragmentTransaction to the back stack.

This way when the back button is hit, the FragmentTransaction is reversed (an added fragment removed, a replaced fragment restored, or a removed fragment re-added). Each transaction added to the back stack is reversed in turn until they’re all removed at which time the default activity finishing behavior again kicks in.

Back isn’t the only navigation button

Of course, the back button isn’t the only navigation button on a modern Android device (no we don’t talk about the menu button anymore).

The home button is probably the most straightforward with its single focus: it puts the current task into the background, taking you back to your launcher.

Note: moving your task to the background does not kill your task (although the topmost activity is most certainly paused+stopped): it’ll live on until the process is killed. Learn more about process priorities and when your app can be killed in the ‘Who lives and who dies?’ blog post.

The overview button (formerly recents), takes you, as you might imagine, to the Overview screen. This is the ‘app switcher’ of the Android world — here you’ll see your most recent tasks and be able to select one to bring it back into the foreground.

Okay, that’s it. Nothing more to see.

Neat, startActivity() or addToBackStack() and the default back button behavior. Nothing special here, but nothing confusing either — these symmetric, consistent behavior serves as the defaults. In most cases, you should be using this default behavior.

Before you go running off and overriding onBackPressed() directly, there’s a few specific cases you might want to consider:

Of course, the back button loses some of its luster when you need to press it 10+ times to get out where you are. One case where this is easy to avoid is when you are launching the same Activity you are currently on.

Instead of creating a stack of multiple copies of the same Activity (which is less fun both from a memory pressure perspective and back button fatigue), your Activity can use launchMode=”singleTop” in the Android manifest or you can add Intent.FLAG_ACTIVITY_SINGLE_TOP to your Intent.

This prevents multiple copies of the same Activity on the top of the back stack. Instead, you’ll get a callback to onNewIntent() with the new Intent and any extras.

Note: Read the documentation on onNewIntent() carefully: getIntent() will still return the original Intent unless you use setIntent() to override it.

If you’re building a notification that points to an Activity deep within your app, there’s one case you want to avoid: tapping the back button exiting directly to the launcher. This occurs when the PendingIntent you provide starts a new task with just the one activity. And unless your notification is opening your launcher Activity, that’s not what you want. The user should be in the exact place they’d be as if they had navigated to that part of the app themselves. Your notification just saved them the intermediate steps.

For something so important, it would be nice to have a class that does all the work for you. Enter TaskStackBuilder: a class specifically for handling the flags and back stack for you for exactly this case:

// Construct the Intent you want to end up at
Intent detailActivity = new Intent(this, DetailActivity.this);
// Construct the PendingIntent for your Notification
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// This uses android:parentActivityName and
// android.support.PARENT_ACTIVITY meta-data by default
stackBuilder.addNextIntentWithParentStack(detailActivity);
PendingIntent pendingIntent = stackBuilder
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

You’ll notice use of the addNextIntentWithParentStack() method — this is the shortcut to building an entire task stack with just passing in the normal Intent you’d have created a PendingIntent from. It does have one requirement though: each Activity needs to have its parent Activity declared in the Android manifest (see the example from the docs).

And you don’t need to throw out TaskStackBuilder if the defaults don’t work in your case: editIntentAt() allows you to retrieve a specific Intent and set the action, set the data URI, or add extras. If you need even more customization, you can forgo using the *ParentStack() methods entirely and use addNextIntent() to directly add the exact Intents you need for your specific case.

Note: as mentioned in the docs, the other type of Activity you might launch from a notification is one specific to the Notification (i.e., not one in your normal application flow). An example of this might be Hangout’s Direct Reply-like Activity used prior to Android N. These activities are generally semi-transparent — you can see the other app below your Activity — and generally don’t have any synthetic back stack or new task associated with them.

Tasks and the back stack, working together

Remember the important part here is being predictable. If you’re messing with your back stack, make sure to test very thoroughly to ensure the best user experience.

#BuildBetterApps

Follow the Android Development Patterns Collection for more!

Android Developers

The official Android Developers publication on Medium

Ian Lake

Written by

Ian Lake

Android Framework Developer at Google and Runner www.google.com/+IanLake

Android Developers

The official Android Developers publication on Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade