Activity is one of the core components of Android framework which, roughly speaking, represents a “screen”. Not necessarily in terms of user interface layout, animations and interactions, but more like an abstraction associated with visual output.

On the first sight, Activity might look simple to understand and use. However, the moment you try to figure out Activity lifecycle, you’re dead in the waters. See, lifecycle is basically a very complex abstract Final State Machine, which allows you to plug your own code into specific states and transitions. Many Android developers, even the ones who have years of experience, don’t know how to write reliable code in the face of Activity lifecycle. In my opinion, insufficient understanding of Activity lifecycle is one of the main causes of complexity and bugs in Android applications.

So, to make your life a bit easier, I’ll share how I work with Activity lifecycle in this post. Please note that I don’t intend to repeat the official documentation or exhaust this topic in some other way. Instead, I want to provide you with concise, practical and actionable guidelines that you can actually remember and use.

onCreate(Bundle)

Whenever you need to switch to a new Activity, you ask Android framework to bring that Activity up, and then the framework instantiates a new Activity object by itself. The reasons for that are irrelevant to our current discussion, but one implication of this mechanism is that your Activities can’t have constructors.

Alright, but, then, where do you initialize your Activities?

Well, you do it in onCreate(Bundle) method. This method should host all the logic that would otherwise reside in Activity’s constructor. Ideally, a constructor will just initialize object’s member fields with injected dependencies:

    public ObjectWithIdealConstructor(FirstDependency firstDependency, 
                                      SecondDependency secondDependency) {
        mFirstDependency = firstDependency;
        mSecondDependency = secondDependency;
    }

[To better understand why the above constructor is ideal read this article by Misko Hevery. Even though Misko discusses the issues associated with “constructors that do too much” in context of unit testing, these constructors are equally bad in contexts of code readability and maintenance.]

In addition to member fields initialization, onCreate(Bundle) has two more responsibilities courtesy of Android framework itself: restore saved state and call setContentView().

So, the general functionality inside onCreate(Bundle) method should be:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getInjector().inject(this); // inject dependencies

        setContentView(R.layout.some_layout);
        
        mSomeView = findViewById(R.id.some_view);

        if (savedInstanceState != null) {
            mWelcomeDialogHasAlreadyBeenShown = savedInstanceState.getBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN);
        }

    }

Evidently, one size doesn’t fit all and you might have a bit different structure. That’s fine, as long as you don’t have the following in onCreate(Bundle):

  • Subscription to observables
  • Functional flows
  • Start of asynchronous functional flows
  • Resources allocations

Whenever I need to decide whether a piece of logic belongs in onCreate(Bundle), I ask myself this question: is this logic related to initialization of this object? If the answer is negative, I find another home for it.

After Activity had been instantiated by the framework and its onCreate(Bundle) method run to completion, it will be in “created” state. “Created” Activity shouldn’t cause allocation of additional resources and shouldn’t receive events from any other object in the system. In this sense, “created” state might be characterized as “ready, but inactive and isolated” state.

onStart()

At some point, due to user interaction, Android framework will call Activity’s onStart() method to let it know that it’s active now. This method is the right place to bring initialized, but inactive and isolated Activity from “created”  state to life.

What exactly “bring to life” means depends on the requirements of each specific Activity. For example, these are some general types of logic that should resides in onStart() method:

  1. Registration of View click listeners (read the update in the discussion of onStop())
  2. Subscription to observables (general observables, not necessarily Rx)
  3. Reflect the current state into UI (UI update)
  4. Functional flows
  5. Start of asynchronous functional flows
  6. Resources allocations

You might be surprised by points #1 and #2 because in most tutorials both of these are done in onCreate(Bundle). In my opinion, this is yet another misunderstanding of the intricacies of Activity lifecycle. We’ll get back to this topic in discussion of onStop() method later in this post.

So, the general structure of onStart() method will be something along these lines:

    @Override
    public void onStart() {
        super.onStart();

        mSomeView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handleOnSomeViewClick();
            }
        });

        mFirstDependency.registerListener(this);

        switch (mSecondDependency.getState()) {
            case SecondDependency.State.STATE_1:
                updateUiAccordingToState1();
                break;
            case SecondDependency.State.STATE_2:
                updateUiAccordingToState2();
                break;
            case SecondDependency.State.STATE_3:
                updateUiAccordingToState3();
                break;
        }

        if (mWelcomeDialogHasAlreadyBeenShown) {
            mFirstDependency.intiateAsyncFunctionalFlowAndNotify();
        } else {
            showWelcomeDialog();
            mWelcomeDialogHasAlreadyBeenShown = true;
        }
    }

After onStart() method returns the Activity will transition from “created” into “started” state. In this state it is visible, functional and can collaborate with other components.

onResume()

The first rule about onResume() method is that you don’t need onResume() method. Remember that. The second rule says that you might indeed need onResume() method in special circumstances. In my opinion, onResume() should only be used to start or resume some moving stuff on the screen. The reason why you’d want to do this in onResume() instead of onStart() will be discussed later in onPause() section.

After this method returns, Activity transitions from “started” state into “resumed” state. This means that the user is interacting with it right now.

onPause()

In this method you should pause or stop on-screen animations and videos that you resumed or started in onResume(). Just like with onResume(), you hardly ever need to override onPause().

The reason why you handle animations in onPause() instead of onStop() is because when Activity is partially hidden by e.g. another transparent Activity representing system dialog or looses focus in multi-window mode, only onPause() will be called. Therefore, if you want to play the animation only when the user actually interacts with this specific Activity, avoid distracting him in multi-window mode and prolong battery life – onPause() will be the only reliable option.

The corollary to this is that if you want your animation or video to continue playing when the user enters multi-window mode, then you shouldn’t pause it in onPause(). In this case, move the logic from onResume()/onPause() into onStart()/onStop().

One special exception that you might want to handle in onResume()/onPause() is camera.

Since camera is a single resource shared among all applications, you’d want to release it in onPause() method to make it accessible to other apps in e.g. multi-window mode. However, in some cases, you might actually want to keep camera to yourself explicitly even when paused (e.g. video chat application). In these special cases, you will need to manage the camera resource in onStart()/onStop().

After this method is called Activity will transition from “resumed” state back into “started” state.

Update: Starting with Android 10, Android allows multiple Activities to be resumed at once. This complicates the lifecycle even further and has serious implications for apps that use camera. I haven’t had enough experience with this feature yet, so can’t give any specific recommendations. Read the linked documentation to get more details.

onStop()

In this method you will unregister all observers and listeners and release all resources that were allocated in onStart().

I usually end up with something along these lines:

    @Override
    public void onStop() {
        super.onStop();

        mSomeView.setOnClickListener(null);

        mFirstDependency.unregisterListener(this);
    }

At this point, let’s discuss why you actually want to unregister in this method (and, consequently, register in onStart()).

First of all, if this Activity would never be unregistered from mFirstDependency then you’d risk leaking it. But why unregister in onStop() and not in, say, onPause() or onDestroy()? It used to be quite tricky to explain why onPause() is not a good candidate, but addition of multi-window mode allowed for a quick and clear explanation.

When the Activity is visible in multi-window mode, the users will expect it to be functional, even if they don’t interact with this Activity at the moment. Otherwise, why would they bother entering multi-window and keeping that Activity on screen? Now, if Activity unregistered from mFirstDependency in onPause(), then it will not receive notifications from that Observable when it’s not focused in multi-window mode. Consequently, it will not be able to reflect these events on the screen, thus completely missing the point of multi-window. Not good.

Unregister in onDestroy() is also not a good option. See, when the application is backgrounded by the user (e.g. click on “home” button), Activity’s onStop() will be called thus returning it to “created” state. Then, Activity can remain in this state for days and even weeks. If mFirstDependency produces a continuous stream of events, this Activity will be handling these events, even though it’s in background. That would be an irresponsible waste of user’s battery life, and can even lead to unexpected side effects. In additino, being registered to mFirstDependency could lead to higher memory consumption, so the entire application would become more appealing to Android’s low memory killer while in background.

So, neither onPause() nor onDestroy() are good places to unregister from external dependencies, therefore you should do it in onStop().

A word about unregistering from View objects.

Due to unfortunate design of Android UI framework, weird scenarios like the one described in this SO question can happen. There are several ways to work around that, but unregistering from UI elements in onStop() is the cleanest one IMHO. Alternatively, you can ignore this rare scenario altogether. That’s what most Android developers do, but, then, your users will occasionally see weird behavior and crashes.

Update: the behavior described above was fixed at Android framework level, and the fix applies retroactively all the way back to API 19. Therefore, you can now safely register listeners to your Viewsin onCreate()and don’t need to unregister them. I decided to keep the old discussion for historical record and for poor souls who still can’t have minSdkVersion=19.

After onStop() method completes Activity transitions from “started” state back to “created” state.

onDestroy()

This method should never be overridden. Literally, never. I just searched in all my projects – not a single place where I needed to override onDestroy().

This makes total sense if you consider what I said about the responsibilities of onCreate(Bundle). Since this method only initializes the Activity object without doing any work, there is nothing that needs to be done manually to clean things up.

When I review new Android codebases, I usually search for several common mistakes and anti-patterns to quickly assess the high-level quality of the code. Overrides of onDestroy() are among the very first code smells that I look for.

onSaveInstanceState(Bundle)

The last piece of mosaic called Activity’s lifecycle that I often use is onSaveInstanceState(Bundle) method.

This method is used to preserve data over configuration change and save & restore flows (also known as “process death”). If you aren’t familiar with these concepts, you should definitely read the article about Android Memory Management and Save & Restore.

The only operations you should do in this method is pushing the state you’d like to preserve into the provided Bundle data structure:

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN, mWelcomeDialogHasAlreadyBeenShown);
    }

In this context I would like to mention one very common pitfall related to Fragments.

If you commit Fragment transactions after this method completed, the application will crash with the notorious IllegalStateException. This is an entire world of ugliness on and by itself and I wouldn’t want to go into its depths right now. If you don’t know what I’m talking about, then you can read this post by Alex Lockwood (though it might be a bit outdated today).

What is important to know is that onSaveInstanceState(Bundle) will be called before onStop().

This means that the Activity remains subscribed to external objects and internal Views after its state is saved. Therefore, if Fragment transaction will be attempted due to invocation of a callback or user interaction in between onSaveInstanceState(Bundle) and onStop() – crash will occur.

There are several approaches to handling these edge cases:

  1. Do nothing and accept that there might be some crashes
  2. Commit Fragment transactions using the method commitAllowingStateLoss() and accept that some state can be lost
  3. Don’t use Fragments at all
  4. Handle each case individually, risking missing some edge scenarios and still get the crashes

The good news are that after years of struggle Google finally acknowledged that this case was basically a bug. The ordering of method calls in Android P will be changed such that onStop() will be called before onSaveInstanceState(Bundle).

This means that if you follow the scheme I described in this post, starting with Android P you will not see IllegalStateException due to Fragment transactions anymore, even if you chose the first approach from the above list.

Edit 1:

Redditor Boza_s6 provided an invaluable feedback on this article:

But even with current behavior I don’t think there’s a problem if everything is cancelled in onStop, because AFAIK onSaveInstanceState and onStop are called as part of same message, so there’s no chance for something to be executed between them.Boza_s6

I have never read or heard about this, but it sounded interesting and important enough to actually give it a thorough investigation. So I opened my copy of AOSP and after some grepping I ended up looking at ActivityThread#performStopActivityInner() method.

Both onSaveInstanceState(Bundle) and onStop() originate from this method. I traced the respective execution paths to make sure that there are no more Runnables posting involved and there weren’t.

Then I dig into AOSP Git history to see since when these methods had been called together.

I’m happy to tell you that this implementation choice was made back in the Honeycomb days. This means that if you use the approach suggested in this post, you’re already safe from crashes due to Fragment transaction commits after state save.

I wouldn’t recommend you write applications based on undocumented implementation detail, but since the ordering of methods will be changed in Android P – you risk absolutely nothing.

So, you can forget about Fragment transactions after state save. You are already safe. Just make sure that you follow the approach I described and your Activities become “inactive and isolated” after onStop().

Edit 2:

Turns out “Edit 1” above is not entirely correct.

There is yet another corner case which was brought up by redditor RaisedByTheInternet. [BTW, androiddev subreddit is one of the best resources for professional Android developers to follow]

As stated on the official Handling Lifecycles page:

On API level 23 and lower, the Android system actually saves the state of an activity even if it is partially covered by another activity. In other words, the Android system calls onSaveInstanceState() but it doesn’t necessarily call onStop(). This creates a potentially long interval where the observer still thinks that the lifecycle is active even though its UI state can’t be modified.

So, there is no “interval” between onSaveInstanceState(Bundle) and onStop() only if the Activity is actually stopped. However, on API 23 and lower, onSaveInstanceState(Bundle) can be called without onStop() if the Activity is being partially obscured by another Activity.

What are the implications of all this?

First of all, “Edit 1” above is incorrect for API 23 and lower because state loss can still happen in partially obscured Activities. This is a very important point and I’m very grateful to redditor RaisedByTheInternet for correcting me.

Secondly, you will still need to choose your strategy from the four options I listed above (before the two edits).

Given the fact that this “partially obscured by other Activity” scenario is rare and this entire issue is not relevant for API 24 and above, options 1 and 2 become much more appealing to me personally.

Conclusion

Alright, time to conclude this article. It ended up being neither short nor simple, but I think it is still simpler and more complete than most of the resources out there (including the official documentation, unfortunately).

In this post I shared “my” approach to Activity lifecycle. If you follow these guidelines in your applications, your Activities will be much cleaner and more reliable. In addition, you’ll have an easier time adjusting to new features. For example, when multi-screen support was added in Android N, this approach required zero adjustments. Furthermore, the change in ordering between onStop() and onSaveInstanceState(Bundle) method calls in Activities in Android P made this scheme the safest for working with Fragments.

In the next post I’ll explain how to handle Fragment’s lifecycle in a similar way.

As always, leave your comments and questions below.

If you liked this post, then you'll surely like my courses

Subscribe for new posts!