Android Activity Lifecycle

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. In my experience, poor understanding of Activity lifecycle is one of the main causes of complexity and bugs in Android applications.

So, in this article, I’ll share how I work with Activity lifecycle. Please note that I don’t intend to repeat the official documentation. Instead, I want to provide you with concise, practical and actionable guidelines that you can actually remember.

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.

Check out my premium

Android Development Courses

28 comments on "Android Activity Lifecycle"

    • Hello Vladchuk,
      I will speculate a bit about the business motivation behind Flutter in one of my upcoming posts, but I don’t intend to try it any time soon.

      Reply
        • Hi Tyler,
          There are many tools I use when developing for Android. Most of them are about productivity, others about quality assurance.

          External tools from the top of my head:
          Emacs – I read all text and code outside of the project I’m working on with it; especially powerful for quick deep dives into AOSP
          Cygwin – linux shell environment emulator for Windows
          Command line Git – I find it quicker and easier to work with Git in command line
          p4merge – my favorite GUI for reviewing Git diffs and resolving merge conflicts
          Fiddler – arguably the best web debugging proxy (too bad it is not available on Macs)

          Libraries and frameworks:
          EventBus – allows for an easy decoupling of components in the app (but can be easily abused)
          GitHub Immutables – generator of immutable data structures
          Stetho – integrated debug bridge; I mostly use it to capture HTTP traffic when testing on real devices

          However, overall, my best tools are design, architecture and unit testing.

          Reply
  1. I’ve never seen anyone setting onClickListener to null before so I’ve never really thought about it, but I guess it makes sense. Would you say it’s actually necessary?

    Reply
    • Hi Ahmad,

      I would say that it is not mandatory because the vast majority of applications don’t do it and work alright. I mean they surely get some amount of errors and crashes due to this unfortunate corner case, but, in general, I don’t think it’s a deal breaker.

      That said, if you can avoid this case with minimal investment then it might be a good idea to do so.

      For example, my approach to presentation layer architecture in Android (summarized in this series of articles) allows for “detaching” of all UI elements with a single line of code. I didn’t know about this corner case when I came up with this approach, so when I discovered it, the fact that I can easily handle it provided a huge validation to my architectural decision.

      Reply
  2. Could you write such a confident article about Fragments lifecycle? Thank you very much for a complete explanation. Much appreciated!

    Reply
    • Hi Dmitriy,
      Thank you for the feedback. Knowing that developers find these long posts useful is very important for me (and, surely, flattering).
      I do intend to write a similar article about Fragments. However, this will take some time.
      See, I’ve been promising my readers an in depth review of Google’s adoption of Kotlin for months now. So, the next three articles will be dedicated to this (business) topic and then, hopefully, I will get back to engineering and write about Fragments.
      Don’t forget to subscribe for post notifications if you’re interested in any of these 😉

      Reply
      • Yeah, that’s a great amount of text I think. The main point is that the post fairly exhausted the topic. I found it rather helpful to complete my view on the lifecycle.

        Reply
      • I also find your articles very useful. I first encountered with your articles about MVP, when I tried to understand the pattern as a newbie developer. Then your theoriy about Flutter and Kotlin and Google killing Android is shared to me. That’s when I subscribed to you. Now I’m here because of your new article about fragments. I really like to understand what am I doing and I want to be a good developer. So far you explained the topics very comprehensible and I was able to understand the logic behind the coding. And this is what most important to me! If I understand the essence of developing, I can reproduce good quality codes anytime I want.

        Reply
  3. Hello, I have found this artical very useful.

    I have a question about “Resources allocations” in onStart method.
    Can you please provide an example for that statement?

    Thank you.

    Reply
  4. I have a question.
    I need to fetch some state from sharedPrefs. Then update the ui based on that state.

    Should I fetch this state in onCreate()?
    It seems like it is the part of initialization of Activity. But, you have also recommended not to have any functional flows in onCreate() so it seems wrong to do all this activity in onCreate(). What would you do in this situation?

    Reply
  5. Thanks for this article.

    I’m curious: At which point in the lifecycle should fragments be instantiated and added?

    I’m unsure of how to sanely handle the fact that an Activity will automatically re-instantiate
    fragments on device configuration changes. I feel like if I’m having to ask if a fragment already
    exists before I add it *every single time I add one*, then I’m probably doing something wrong!

    Reply
    • Hello,
      If you add Fragments in code, then, indeed, you’ll need to differentiate between the first launch of the Activity and subsequent launches. The simplest way of doing that is to check whether saved instance Bundle passed into Activity’s onCreate() method is null. If it is, then you need to add the Fragments; if not, then you know that Android will re-create the previously existing Fragments itself.
      It’s clumsy, but that’s the way it works (

      Reply
  6. Hi Vasiliy,
    I was searching for an article like this from when. Clear, to the point and precise explanation as always. Thanks again.

    I had one doubt.

    “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.”

    I did not understand how unregistering from UI elements in onStop() will solve the issue mentioned in SO question. I actually tried the mentioned scenario and the logs are as follows:

    D/Jagz: A onStart
    D/Jagz: A onResume
    D/Jagz: btnListener: btnA
    D/Jagz: A onPause
    D/Jagz: B onStart
    D/Jagz: B onResume
    D/Jagz: btnListener: btnB
    D/Jagz: A onStop

    We can see here that the onClick for btnB was called before the call to onStop, so even when the clicklistener is removed on onStop the ship to the issue has already sailed. What am I understanding wrong here ?

    Reply
      • Hi Vasiliy,

        first of all, thanks for that great article.
        Actually, while reading it I came up with the same question as Jagannath Patil. Watching the output of Jagannath Patil shows how onStop() is called after the listener for Button B. So also to my understanding, unregistering the listener in onStop() does not solve the issue. Same goes for the change Google accounced in the Twitter post you referenced: “In the realm of ‘bug fixes you never knew you needed’, we now cancel pending click events in onStop()” So that change is also in onStop(). Could you please have a second look on that and how unregistering the listener in onStop() should fix the issues described on Stackoverflow (https://stackoverflow.com/questions/38368391/android-click-event-after-activity-onpause) ? Also, in my case, my app shall support API level 15, so I can’t use the “fix” (?) introduced by Google in API19. Therefore would be even more interested how to fix it myself.

        Thank you and keep on writing great articles 🙂

        Reply
        • Hello Stefan,
          As I explained in the article, you need to make sure that you don’t get new user input notifications after onStop(). In Jagannath Patil’s example, the listener for button B is fired before onStop(), so it’s a valid call. If your goal is to make sure that no user input will be captured after you start “navigation flow” (or any other flow, really), then you’ll need to unregister listeners when you invoke that flow, and not just in onStop(). Google’s change provides a similar guarantees with respect to onStop().
          In the referenced StackOverflow question OP said that they have problem because the click listener is fired after onPause(). In their case, the problem is not just click listeners, but also incorrect lifecycle handling. They probably use onPause() instead of onStop(). Not their fault, though, because this incorrect approach was promoted by the official guidelines for years (basically until they rolled out LiveData).
          All in all, you shouldn’t have problems if you follow the guidelines from this post.

          Reply
  7. Thank you for this great article!!

    One doubt though, what about handling sensors and others? The official docs says: “You can also use the onPause() method to release system resources, handles to sensors (like GPS), or any resources that may affect battery life while your activity is paused and the user does not need them.”

    Do you still recommend to do these things in the onStart and onStop?

    Reply
    • Hey David,
      It’s up to you to decide when to release resources in your app. If you release in onPause(), then the sensor won’t work when other apps’ dialogs obscure yours, or when your app is shown in multi-window, but is not the “focused” one, etc. If that’s what you want, then go ahead.
      However, I strongly recommend being consistent and do it in onStop(), unless there are special cases like camera which are known to have issues.

      Reply
  8. Great Article, Thanx

    Question: In my Activity Where should I start observer for LiveData present inside my ViewModel.
    1. onCreate()
    2. onStart()

    Follow up question, what about in case of fragments.
    1.onCreateView()
    2.onViewCreated()
    3.onStart()

    Reply
    • Hello,
      I don’t use LiveData in my projects, but if I would, I’d probably subscribe to it in onCreate() of both Activity and Fragment after initializing the reference to ViewModel. This assumes that you use the subscription variant with LifecycleOwner. If not, then back to onStart()/onStop().

      Reply
  9. Hi Vasiliy,

    Great article as always 🙂

    I have a question related to application lifecycle.
    You mentioned that you do not use LiveData in your projects. In MVP architecture if we use RxJava for domain and data layer requests. Technically after onPause or onStop there is no reason to deliver updates to UI in the cases where a network operation still executes.

    In cases where you load data, one option is to cache that result and present it to the UI when the user is back, but how would you handle cases where there was an actual operation done by the user (such as place an order or update his profile). Do you keep and manage this action state at the presenter level ?

    I did not see may options to handle this without LiveData.

    Thanks
    Adrian

    Reply
    • Hello Adrian,
      The exact implementation will depend on the specific requirements.
      For example, if you need to implement full offline support, then “changing the profile” will write new data to a local database and then sync it with the server at a later time (or immediately, if the connection is available). If we aren’t talking about offline support, then I’d implement a state machine with a state UPDATING_PROFILE. If the presents goes through onStart() and finds out that this is the current state, then it will know to check whether the update operation was completed.

      Reply

Leave a Comment