Activities in the Wild: Exploring the Activity Lifecycle

Running, or paused, or stopped, or destroyed? Oh my! As Android developers, we have little control over what state our activities are in. But we do have the ability to hook into transitions between these states to respond appropriately and ensure a pleasant user experience.

In this 360AnDev talk, through concrete examples, you will learn how the creation and destruction of your activity instances relate to what the user is doing, as well as to what the system is doing. We will explore how state is persisted (or not) in these various scenarios and also explore some little-known facts about what happens to your activity in low-memory situations. Finally, we will discuss how knowing when these transitions happen helped me deal with tricky situations, like continuing video playback while the user interacts with a full-screen overlay.


Introduction (0:00)

I’m Kristin Marsicano. I am here to talk to you today about one of my favorite topics in Android, which is the activity lifecycle. I first want to do a little expectation setting. I have set up this presentation for beginners, but my hope is that even if you’re not a beginner that you can still get something out of this. At the very least, it’s a semi-entertaining review of a very important topic.

What is the Activity Lifecycle (0:46)

I want to start with a motivating example. I’ve written this app called Ticker Tally. Let’s image I’m working at the bar and I’m the bouncer, and I’m counting how many people are coming in. Rather than having this mechanical device, I’ve written an Android app to do it on my phone. I am counting people as they’re coming in, and all is great and well until I rotate my device.

Now I’ve only counted seven people. I probably wouldn’t be too upset, but if I’ve been doing this all night, and I haven’t kept track in my mind of the number, I have lost my information. And as a user, I’m going to be really upset.

My goal for today is to empower you to reason about the activity lifecycle so that you can avoid common problems related to it, like state loss on rotation. So that you can reason about how you want to structure your activity code, but most importantly, to provide a smooth experience for your user.

Let’s talk about what the heck is this activity lifecycle that I keep referencing. We’ll start with an Activity. An activity roughly speaking represents a single screen that your user is interacting with. As a developer, you probably know even if you’ve only just done a Hello, World! tutorial, this means you’re writing some Java class that’s extending from some framework activity class.

On slide 6 you can see I have a TickerActivity class. Inside it, I have this instance variable called mCount, and I’m setting it to zero. Whenever I create a new ticker activity, I am initializing my count to zero and then I’ve got some other stuff going on. I’ve got this onCreate method, which you may or may not have seen, and I’m doing some setup with my UI.

What’s not shown here is when the user’s pressing the button, I’m incrementing the value of that mCount. That variable becomes very important throughout this presentation. Here’s the thing about activities. You write all this code, and that’s great, but you will never create an instance of your activity. Instead, you rely on the operating system to create instances of it at run time based on what your user’s doing. The operating system does it, in response to something called an intent.

Activity Manager (3:00)

Here’s a simple example. The user clicks on the ticker icon on the launch screen. That issues a request and intent to start my ticker activity. There’s something that lives in the operating system called an Activity Manager, which is listening for these requests and it’s going to spin up a process for my activities to live in. It’s going to create an instance of ticker activity. Then, it’s going to move my ticker activity to what we call the running state. Now the user sees my ticker app, and they can interact with it.

Really what matters for this talk is that every application on your device gets its own process that it runs in and for us. What we care about is that’s where your activity instances will live. It’s that little piece of memory that’s used to store your activity instances when our ticker activity was launched. We got a process for our application. A ticker activity instance was created and then it was moved to the running state, so the user can interact with it. A key rule about activities: only one can ever be running in the foreground at a time.

Activity States (4:26)

What are the states that are part of this activity lifecycle? I think of them as four main ones. There’s three outlined in the documentation, but ultimately, we have: running, paused, stopped, nonexistent.

There are three key things to know about each state:

  • Do I have an instance of my activity in memory? That matters for things like when I have a count variable that I’m relying on being there.
  • Is there an activity instance alive that’s holding that information for me? Is my activity visible to the user? Can they see my activity?
  • And finally, is my activity in the foreground? Is it that one activity that my user, that has user focus, that they’re actively interacting with?

We’ll go through these in more detail throughout this presentation, but a quick overview here:

  • Nonexistent: this is where your activity is not created at all. It’s not in memory, and the user can’t see it.
  • Stopped: this is where there’s an instance of your activity in memory, so any instance variables you’ve set up in there are accessible, they’re in alive, but it’s not visible to the user.
  • Paused: this means that there’s an instance in memory, and now the user can see your activity and in some cases, they can only partially see it, but it’s not in the foreground. It doesn’t have user focus. It’s not what they’re interacting with.
  • Running: this is also called resumed if you check out the documentation. It’s running, and it is in the foreground. That is where your activity is shining, and the user is counting the number of people walking past them.

Activity in the Wild (6:13)

Here’s the thing about activities. They’re super fundamental. To your user, your application is an activity. But you have very little control at runtime what state your activity’s in. Instead, it depends on what the user is doing and also in part, what’s going on in the rest of the operating system. The thing is, while we don’t have control about what state we’re in, this lifecycle is composed of a set of rules about how the state changes based on what’s going on with the user in the system. And more importantly, a set of callbacks that you can hook into. These callbacks are really just methods that the operating system is going to call on your activity as your activity is moved through these states. You’re being notified, hey, this state is changing.

I think that the activity lifecycle is best understood by example, and so I want you to take a journey with me and let’s pretend we are the ticker activity. We’re going to step through five examples, and I’m going to show you how the activity moves between these states based on with the user.

Lifecycle by Example 1 of 5: “Build-up” (6:59)

The first example is super simple. We’re going to launch the activity. The user clicks on our icon, and the ticker activity is launched. For this, you’re going to see this same sort of format of slides over and over, so I want to make sure you understand where I’m trying to go and what I’m trying to represent. The little box on slide 42 that says “Android OS,” that is where I’m going to show you what’s going on from a process perspective. What processes are alive and what activities are showing? That is simplified because at any given time you’ve got a lot of processes running on your Android device. We’re going to try to focus on ticker activity in the ticker app.

Then, on the right side, I’ve got a screenshot of what the user is actually seeing at this point. Then, at the top, I’m going to build across a state diagram that shows you what’s going on regarding state for that ticker activity.

We start out in nonexistent state. There’s no instance in memory. There’s nothing in that little Android OS box, and the user doesn’t see your activity. It’s not even alive. The user presses your icon, and the operating system creates an instance of our activity and calls onCreate, which moves us into this stop state. Notice the user does not see your activity yet. Then, onStart is called immediately following this. It moves you to pause state. Now the user can see your UI, but it’s not in the foreground. Then finally onResume is called to move you to running state, which now means the user sees your activity, and it’s ready for the user to interact with it.

Lifecycle by Example 2 of 5: “Tear down” (8:52)

Now let’s talk about pressing back. I often call this the tear down cycle. The user is going to increment their count to one. Now notice that mCount variable has a one in it and that is what’s reflected in my UI. When the user presses back, it’s going to move us state by state to nonexistent. It’ll call onPause to move us to pause state, so now your activity is visible but not in the foreground. It calls onStop to move us to the stopped state, so now your activity is not visible, but the instance is still there in memory. Then finally, it calls onDestroy. The instance is destroyed, and the user no longer sees it anymore.

I have a question for you. We’ve launched our ticker, we’ve pressed once, and now I have pressed back, and I launch the app again. What is the user going to see? Are they going to see my count that I had counted up to, in this case, one? Or are they going to see my initialized count of zero? It’s going to be zero.

The key here is that your activity instance was destroyed. That mCount got destroyed along with it. There is no notion of that state anymore, so it has to recreate your activity from scratch. That is what the lifecycle would look like. We were in the nonexistent state, and it moved us through onCreate, onStart, onResume to running, and created a new instance of our activity with a count of zero.

Lifecycle by Example 3 of 5: “Switching Tasks” (10:37)

Let’s try a similar example. When the user presses home, it’s different than pressing back. When you press home as an Android user, you’re saying, “I’m not done with this activity, and I may come back. I’m switching tasks.” The operating system is not going to fully destroy your activity. Instead, it’s going to hold onto that instance and any state you’ve stashed in it.

I increment my count twice, and I press home. What happens is my ticker activity is running. I’ve got a count of two. I press home. It calls onPause and moves me to the pause state, and I can still see it. But it’s not in the foreground. I can still see my activity on the screen. It calls onStop to move me to the stop state, so now the user cannot see my activity, but the instance is still alive. What’s nice about this is then I can do other things. I can maybe edit a contact or send an email, look at Twitter, and this process is hanging around with my activity in it. I’ve pressed home.

I do other stuff, and then I decide to relaunch my app from this current state. What is the user going to see? Am I going to see my initialized state of zero or am I going to see whatever work I’ve done? In this case, I’ve counted up to two. Two is the answer. We have this activity instance and really what’s happening now is it’s getting moved from stopped, the not visible state, to running, so now it’s in the foreground and the user, it’s visible, it’s in the foreground. The user can interact with it.

Lifecycle by Example 4 of 5: “Fully oclude TickerActivity” (12:35)

Another example, launching another activity. This is an example where I’m going to press the ticker icon and launch Terrific Ticker because now I can save my counts. I increment it three times, and then I click save. That is going to launch a new activity within my application, which will allow me to enter a file name and save.

We’re in our running state. Again, we are looking at the state diagram representing what the ticker activity is doing. The user presses save count. What’ll happen now is a saved activity gets created and the saved activity gets run through its lifecycle, so it is running and in the foreground.

What did I say one of the most important rules about activities is? Only one activity can be running in the foreground at a time. Something must happen to the state of our ticker activity and what’ll happen is it gets moved from running to stopped. Now, if you think about it, I said stopped means your activity instance is alive in memory, but the user cannot see it. In this scenario, if the user sees that top screenshot, they just see your saved activity. They don’t see ticker. Ticker moves from running through onPause through onStop to stopped.

Now, in this case, the user now is going to press back on our saved activity. Saved activity is going to get destroyed. Our ticker activity is going to come to the foreground. It’s going to move from stopped state into paused state through onStart. Now notice the ticker activity can be seen. When we call onResume it moves to running, and now it can be actively interacted with.

Lifecycle by Example 5 of 5: “Partially oclude TickerActivity” (15:02)

One more example, but now I’ve upgraded my saved activity because, for some reason, I think it’s really important when you click save to have context about what you’re saving. Now you can see the ticker activity behind a semi-transparent save activity.

Think to yourself for a minute, what state might ticker activity be in? Ticker activity is in running state, and we click save count. It’s going to launch the save activity running in the foreground, which means that’s going to kick our ticker activity out of running state. What’ll happen is it will call onPause and move our activity to the paused state, and that’s it. It’s paused because it’s not in the foreground, but you can see it. You can’t see it fully, but you can partially see it. Now the user presses back. Saved activity goes away, and our ticker activity has fewer steps to get back to running. Now, only onResume is called.

What I’m trying to demonstrate here is that it’s not so simple as you’re always building entirely up and down. It’s possible that you might oscillate between paused and running many times, or stopped -> paused -> running many times throughout the life of your activity as the user interacts with it.

Lifecycle summary (16:37)

Let’s summarize for a moment what is going on with this lifecycle overall. I like to think of it as three key sub-lifecycles.

On slide 103 you can see our entire lifecycle, but there’s this notion of the entire lifetime. Is an instance of my activity alive in memory? That happens between onCreate and onDestroy and everywhere in between, an instance of your activity is living in memory.

Then there’s the notion of the visible lifetime. Can the user see my activity? That happens between onStart and onStop and everywhere in between, so the user can see the screen, but might not necessarily have it in the foreground.

Finally, there’s the foreground lifetime, which is between onResume and onPause. It represents the running state, and this is where your activity is the shining star of the moment, and it has your user’s full attention.

Now I want to revisit one of our lifecycle walks, which was going home. This is a cautionary tale. We’ve learned about the lifecycle. We understand what the states mean, and we’ve got our app, and we’ve done lots of work. Maybe I’ve incremented my counter to 42 or 42,000 or whatever, and I press the home button. When I press home, again, I’m saying as a user, “I’m not done here. I might come back, so I’m expecting when I come back to your app, that I’m going to see my count at 42.” I press home, and I launch Pokemon Go. It’s slow here at the bar. Not many people are walking by, so I’m going to keep myself busy. Then, someone shows up, and I go back to my ticker. Oh no. I pressed home, so I’m expecting 42, but I see zero. What the heck happened? Let’s talk about it.

So user, our current state this is representing right now, is that the user is on our ticker activity. Here I’ve incremented my count to three, and I’m in the running state. I press home, which moves me to my stopped state, and then I do something like play Pokemon Go. Now a process gets spun up as you see in this Android OS box over here for Pokemon Go, and there’s some activity or who knows what they’re called?

What’s going on in Pokemon Go, we don’t really care, but ultimately, life is going fine, but then the system says, “Oh-oh. This Pokemon Go takes up a lot of resources. I need a lot of memory for people to play this game.” It says, “All right, well, let me figure out what things can I get rid of?” So it’s going to look at your process and say, “Well, they’re not actively interacting with this ticker activity in this ticker process. I’m just going to get rid of it.” It is going to get rid of your process along with any activities. Any state you have in those activities, all that gets destroyed. Also of note, on Destroy’s not going to get called. It is going to hop you from stopped. You’re going to be nonexistent, and onDestroy never gets called.

What is a woeful Android developer to do? Lucky for us, there’s a mechanism to deal with these sorts of situations called “saved instance state.”

Saving Transient State (20:06)

public class TickerActivity extends AppCompatActivity {
    private static final String KEY_TICKER_COUNT = "ticker_count";
    ...

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(KEY_TICKER_COUNT, mCount);
    }
    
    ...
}

There’s an onSaveInstanceState callback method that the operating system will call on your behalf to stash state from your activity. The method itself looks like this. We have onSaveInstanceState. There’s an input. Bundle is a mapping of keys to values. I have outState.putInt, so I’m putting my count and stashing it in this bundle of state.

public class TickerActivity extends AppCompatActivity {

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ticker);
        setupUI();

        if (savedInstanceState != null) {
            mCount = savedInstanceState.getInt(KEY_TICKER_COUNT, 0);
        }
    
        updateUI();
    }
    ...
}

onCreate has an input that accepts a Bundle. If onSaveInstanceState is called, whatever you stash will be passed back into onCreate when the activity’s recreated. As a developer, you should be saving your state; they call it the transient state. Any UI-related state that the user would expect to be there if they hadn’t explicitly dismissed the activity. You should be checking, do I have a saved instance state? If so, pull it out and set up your UI accordingly.

It looks something like this. I’ve got Instance A of my activity living in memory, and the user presses home. My activity is moved to onStop, and onSaveInstanceState is called on my behalf because the user, the activity’s no longer in the foreground, but as a user, I expected that activity is not going to die. My work should still be there. I’m not done yet. I did not push back.

So onSaveInstanceState gets called, which will ultimately write something called an activity record. That is living in the operating system outside of the life of my app or my activity, and I can stash information that I can use to recreate the state of my activity.

Now I go and play Pokemon Go. My ticker activity process gets killed while I’m playing Pokemon Go, but it’s okay because I have an activity record that represents the state that the user was in. When I relaunch my ticker app, ticker activity, its onCreate is going to receive a bundle with my ticker count sent back in. Then I can update that view accordingly. That is a recreated instance in memory of A. It’s really a whole new object all together that was created for me, but I was able to use information to make it look to the user that it was never destroyed.

Let’s take this full circle here. This slide has lots of meanings about full circle, but ultimately, we started out with an example where I rotated my device, and my state got lost. That’s because the operating system, when you rotate, it treats us as what we call a runtime configuration. Whenever there’s a runtime configuration, Android wants to do its best to recreate things to look best in this new configuration. The most common configuration you could ever think of is rotation. So landscape and portrait are different runtime configurations, so the operating system will fully destroy your activity. It will run it through the entire tear down lifecycle and then it will run it through the entire build up lifecycle. It will create a whole new activity.

If you are using onSaveInstanceState, it will use this same mechanism just like it did when your process is destroyed. onSaveInstanceState is going to get called whenever the user is not the one dismissing your activity. If I press back, do you think saved instance state is going to get called? No, because back is really the user’s mechanism for saying, “I’m done.” But in most other cases, like pressing home, switching tasks and rotation, it’s going to call onSaveInstanceState for you.

Structuring your Activity (24:18)

Let’s talk about structuring your activity. For me, I am a very detail-oriented person, and I want a formula for everything. I want to have concrete examples. I want to know every single possible combination like in onCreate, you must do this and onStart, you must do that. In reality, it’s not that simple. There are some guidelines, and there’s definitely things people do in practice.

onCreate. You’re almost always going to set content view to some XML layout resource. You’re going to do what I call hook up UI widgets, so you’re going to get references to buttons and set onClick listeners and things like that. You should be checking for saved state if that’s relevant for your app if your user had a state that they’re expecting to still be there.

onPause. Often this is the place people will tell you that you need to save your vital data. It represents your app going out of the foreground. If there’s things that are really important that your user cares about that you need to persist, not in memory, like on disc or to web or whatever, then you may want to consider onPause. You might also want to think about stopping things that are consuming resources. We’re going to talk about location. Anything that’s taking up memory or CPU. You may want to consider stopping those things because when your activity’s going out of the foreground, it might not make sense for you to still be consuming them. It might not make sense to still be pulling for location and things you do in onPause.

Any lifecycle method should be quick because they’re all running on what we call our main UI thread, which is a whole other talk, but it should be quick because mostly also with onPause, any other activity that might be launching over this one cannot even resume until this returns.

onStop is actually now our last guaranteed tear-down method. So you saw that example where the process got killed and onDestroy was not called. onStop really is your last guaranteed callback. Depending upon the requirements of what you’re doing, maybe this might be the right spot to persist vital data.

onDestroy, I think of that as the last place to free up resources. If you’ve got some background thread you’ve spun up in onCreate and you’re queuing a request to do something, in onDestroy you would want to release that. That way you don’t have any memory leaks or things hanging around when they shouldn’t be. But even though it’s not guaranteed to be called, if you think about that example of a background thread living in your process, if onDestroy’s not called, your process got killed. So did everything else in it.

But really, my point is it depends. Reasoning about what to put in the lifecycle methods is a marriage of your understanding of the states and what they mean to the user and what they mean regarding your instance being in memory and along with what the requirements of your application are with respect to what the user is doing.

Reasoning About the Lifecycle (27:36)

I wanted to go through some concrete examples of reasoning about the activity lifecycle and the callbacks and what you do. Let’s talk about displaying location.

I’ve got an app, and I want to show the user their location. I want to minimize, of course, the amount of battery power I’m using because now there’s that screen of shame where people can see how your app is performing on consuming battery. But I want to maximize precision, right? I want to give my user the best experience. I want to have the location ready, and I want it to be really, really accurate. I’ve got the callback/lifecycle diagram on slide 145 as a point of reference.

When my activity is started, I might want to start sampling. I have onCreate, but when I’m in the stopped state, can my user see my screen at all? No. If they can’t see my screen, I might not care about sampling for location data, so I’ll start sampling onStart and then I will stop sampling. The very bottom bullet there, in onStop.

One thing also in practice you learn is that you should be mirroring build up and tear down of things. If you’re registering a listener and then an unregistering it, you should be doing it in the corresponding lifecycle method. onStart corresponds, you can see in the diagram, with onStop. onResume with onPause and onCreate with onDestroy.

Then when the user is moved to the running state, which means that my activity’s actually in the foreground, they’re actively interacting with it, I might choose to, rather than do my normal run of the mill sampling, I’m going to increase the sampling rate. I’m reading location faster, and I’m going to increase the accuracy. This takes more resources, but it gives my user a better experience.

So you can use these lifecycles to tap into things like this. My user’s looking at the app. I want it to be super accurate. I don’t care how much battery I’m using, but when I step back, and it’s partially visible, that might be another scenario. Maybe you want to downgrade your accuracy and your sampling rate a little bit.

Here’s another example, video playback. That is near and dear to my heart because I’ve worked on an app that had a lot of video playback. One would think when onPause is called, I’d want to pause my video. Not necessarily.

Let’s say my requirements are that when my user is watching the video they can launch another activity. It’s a full-screen activity, but it’s transparent, and they can tweak things about the subtitle settings and who knows what else. Well, since my settings activity, it’s in the foreground, but it’s transparent, what does that mean for my video activity? It’s paused, right? So if I’m stopping playback on pause, but I’m expecting the video to play in pause when the activity is paused, that’s not going to work. Instead, you would want to start playback. Start your playback in onStart perhaps. Maybe you wouldn’t start it onStart.

We’ll go back to that, but ultimately, you don’t want to stop or pause playback until the activity is no longer visible, which represents the stop state. Then, there’re other things like resources. With video players, a lot of times, you have to initialize things and tear them down, and onCreate and onDestroy would be a good opportunity to do that.

Capstone Challenge / Conclusion (31:14)

I wanted to have one capstone challenge on slide 150. I’m going to ask you what state do you think these activities are in? Multi-window. I’ve got two activities here now. I’ve got on the right this maps activity that I’m actively interacting with and on the left, I’ve got Chrome where I’m viewing information about perhaps the location that I’m looking for. What I want to know, I’m actively interacting with the maps, but I can still see Chrome, which state do you think the maps activity is in? It’s paused.

You knew this; I didn’t have to tell you because you know now that when something is paused, the user can see it. If not wholly than partially, but it’s not in the foreground, and we know that only one activity can be running in the foreground at a time.

My point here here on the activity lifecycle is that when you understand how it relates to what the user is doing and what it means to the system, then you start to do things with a reason behind them. Then when new big changes like this come along, it’s more than likely that your application is going to behave appropriately.

Next steps. I highly recommend and love reading the Android documentation. People make fun of me because I read it so much, but if you’re interested in Android developing at all, and you’re a beginner, please go and look at the online documentation. Engage in local communities. Don’t be afraid to ask questions. A lot of things in Android can be tricky when you’re first starting, but with a little guidance and a lot of gumption, you can definitely figure it out.

I didn’t say this in the beginning, but I work at Big Nerd Ranch. We teach week-long Android boot camps, so if you want to come spend a week and learn Android with me, you’re welcome to.


Kristin Marsicano

Kristin Marsicano

Kristin Marsicano is an Android developer at Big Nerd Ranch, and the co-author of Android Programming: The Big Nerd Ranch Guide (2nd Edition). Prior to working at Big Nerd Ranch, Kristin spent five years teaching introductory Computer Science courses at Georgia Tech. Always an educator at heart, she is passionate about learning, software development, and the intersection of the two. When she is not teaching or developing apps, you can find Kristin cooking for her growing family, doing yoga, or learning something new (like gardening).