- Android Activity Life-cycle for Professional Developers
- Vasiliy Zukanov
You read that right, it’s 2018, and I’m writing an article about the Activity life cycle on Android that has surprised even me.
At first, I felt that the Activity lifecycle, while overly complex, should not be a problem. What I mean by that is: Navigating the Activity life cycle correctly can be a bit difficult for new Android developers, but I can’t imagine it still being a thorny issue for experienced Android developers.
I still think it’s too simple.
I’ll tell you the whole story later, but first let me sketch my purpose for writing this article.
I want to share with you how I handle the Activity lifecycle, which is simpler than described in the official documentation and covers most of the tricky extremes.
The Activity lifecycle remains a challenge:
Two things happened within a week that made me realize that even today, the Activity life cycle is still a challenge for Android developers.
A Redditor shared an article on AndroidDev a few weeks ago about the Activity lifecycle. This post is based on an answer I shared in the StackOverflow community. The questioner suggested a way to handle the Activity lifecycle that I felt was inaccurate, so I immediately submitted a comment in the hope that other readers would be aware of this.
Then several Redditors answered and challenged my point, and it turned into a long and very insightful discussion. During the discussion, I observed some interesting things about the Activity lifecycle:
- The Activity lifecycle confuses many experienced Android developers.
- The official documentation still contains outdated and contradictory information about the Activity’s life cycle.
- Even the developers who write the official Google tutorials don’t really understand the Activity lifecycle and its impact.
A few days later, I was visiting a potential client. The company hired a team of experienced Android developers who tried to add new features to the old code base, which wasn’t an easy task.
During a quick code review, I noticed that one of the maintainers decided to subscribe to EventBus in the Activity’s onCreate (Bundle) and unsubscribe in onDestroy (). Doing so would cause a lot of trouble, so I suggested they refactor that part of the code.
My take on Activity life-cycle::
These two things made me realize that many Android developers are still confused about the Activity lifecycle. Now, I want to try to make it easier for other developers by sharing my experience.
I’m not going to re-document the Activity lifecycle here; it’s too expensive. I’ll show you how to divide logic between methods in the Activity lifecycle to achieve the simplest design and avoid the most common pitfalls.
onCreate(Bundle):
The Android Framework does not provide an Activity constructor, which automatically creates an instance of the Activity class. So where should we start our Activity?
You can put all the logic that should be handled in the Activity constructor in onCreate(Bundle).
Ideally, the constructor only initializes the object’s member variables:
public ObjectWithIdealConstructor(FirstDependency firstDependency,
SecondDependency secondDependency) {
mFirstDependency = firstDependency;
mSecondDependency = secondDependency;
}
Copy the code
In addition to initializing member variables, the onCreate (Bundle) method has two other responsibilities: restoring the previously saved state and calling the setContentView() method.
So, the basic functional structure in the 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);
}
}
Copy the code
Obviously, this doesn’t work for everyone, and you might have some different structures. But that’s okay, as long as you don’t have the following in onCreate (Bundle) :
- Subscription to observables
- Functional flows
- Initialization of asynchronous functional flows
- Resources allocations
Whenever I need to decide whether a certain piece of logic should be placed in the onCreate (Bundle) method, I ask myself this question: does this piece of logic have anything to do with the initialization of the Activity? If the answer is no, I’ll look elsewhere.
When the Android Framework creates an instance of an Activity and its onCreate(Bundle) method completes execution, the Activity is created.
An Activity in the created state does not trigger resource allocation, nor does it receive events from other objects in the system. In this sense, a “created” state is one that is ready, but inactive and isolated.
onStart():
To enable an Activity to interact with the user, the Android Framework then calls the Activity’s onStart() method to make it active. Some basic logical processing in the onStart() method includes:
- Registration of View click listeners
- Subscription to observables (general observables, not necessarily Rx)
- Reflect the current state into UI (UI update)
- Functional flows
- Initialization of asynchronous functional flows
- Resources allocations
You may be surprised at points 1 and 2. Because in most official documentation and tutorials, they’re put in onCreate(Bundle). I think this is a misunderstanding of the complex Activity lifecycle, which I’ll cover next in the onStop() method.
So, the basic functional structure in the onStart () method should be:
@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; }}Copy the code
It’s important to note that what you actually do in onStart () is determined by the detailed requirements of each particular Activity.
After the Android Framework calls the onStart () method, the Activity transitions from the created state to the started state. In this state, it works and can collaborate with other components of the system.
onResume():
The first rule about the onResume () method is that you do not need to override the onResume () method. The second rule is that you do not need to override the onResume () method. The third rule is that you really need to override the onResume () method only in special cases.
I searched the code base for one of my projects and found 32 onStart () methods overwritten, averaging about 5 lines per code, for a total of about 150 + lines. Instead, only two onResume () methods are overwritten, with a total of eight lines of code, which are mainly used to resume playing animations and videos.
This sums up my view of onResume () : it can only be used to start or restore dynamic objects on the screen. The reason you want to do this in onResume () instead of onStart () will be discussed later in the onPause () section.
After the Android Framework calls the onResume () method, the Activity transitions from the started state to the “resumed” state. In this state, users can interact with it.
onPause():
In this method, you should pause or stop animations and videos that resume or start in onResume (). Just like onResume (), you hardly need to override onPause (). The reason for handling animation in onPause () rather than onStop () is that only onPause () is called when the Activity is partially hidden (for example, popping up a system dialog) or is out of focus in multi-window mode. So if you want to play animations when only the user is interacting with the Activity, while avoiding distracting the user in multi-window mode and extending battery life, onPause() is the only option you can rely on. The premise of this conclusion is that you want your animation or video to stop playing when the user enters multi-window mode. If you want your animation or video to continue playing when the user enters multi-window mode, then you should not pause it in onPause (). In this case, you need to move the logic in onResume ()/onPause () to onStart ()/onStop ().
One special case is the use of a camera, which is a single resource shared by all applications, so you usually want to release it in onPause ().
After the Android Framework calls onPause (), the Activity transitions from the “resumed” state to the started state.
onStop():
In this method, you unregister all observers and listeners and release all resources allocated in onStart ().
So, the basic structure of the onStop () method should be:
@Override
public void onStop() {
super.onStop();
mSomeView.setOnClickListener(null);
mFirstDependency.unregisterListener(this);
}
Copy the code
Let’s discuss a question: Why do you need to unregister in this method?
First, you might leak this Activity if it is not unregistered by the mFirstDependency. It’s not a risk I’m willing to take.
So, the question becomes why unregister onStop () instead of onPause () or onDestroy ()?
It’s a little tricky to explain this quickly and clearly.
If in onPause () method calls in mFirstDependency. UnregisterListener (this), then the Activity will not receive the related asynchronous notification process completed. Therefore, it does not make the user aware of this event, which completely defeats the purpose of multi-window mode, which is not a good way to handle it.
If in the onDestroy () method calls in mFirstDependency. UnregisterListener (this), it also is not a good way to handle.
When the application is pushed into the background by the user (for example, by clicking the “home” button), the Activity’s onStop () is called, returning it to the “created” state, which the Activity can maintain for days or even weeks.
If mFirstDependency produces a continuous stream of events at this point, the Activity can handle those events for days or even weeks, even if the user never actually interacts with it during that time. This would be an irresponsible waste Of the user’s battery life, and in this case, the app would gradually consume more Memory, and the application would be more likely to be killed by OOM (Out Of Memory) Killer.
Therefore, in the onPause () and onDestroy () call mFirstDependency. UnregisterListener (this) is not a good way to handle it, you should be in onStop () to perform this operation.
I want to say a few more words about unlogging the View’s event listener.
Due to the poor design of the Android UI framework, strange scenarios like the one described in this question can occur. There are several ways to solve this problem, but unregistering the View’s event listener from onStop () is the cleanest one.
Or, you can ignore this rare situation entirely. This is what most Android developers do, but your users will occasionally experience strange behavior and crashes.
After the Android Framework calls the onStop () method, the Activity transitions from the started state to the created state.
onDestroy():
Never override this method.
I searched all my project code bases, and nowhere did I need to override the onDestroy () method.
If you think about my previous description of onCreate (Bundle) responsibilities, you’ll see that this makes perfect sense. It just initializes the Activity object, so there’s no need to manually clean it up.
When I look at a new Android code base, I usually search for a few common error patterns, which give me a quick idea of the quality of the code. The override of onDestroy () is the first error pattern I look for.
onSaveInstanceState(Bundle):
This method is used to store some temporary state. In this method, all you need to do is store the state you want to store in the Bundle data structure:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVED_STATE_WELCOME_DIALOG_SHOWN, mWelcomeDialogHasAlreadyBeenShown);
}
Copy the code
Here, I’d like to mention a common pitfall associated with fragments.
If you commit a Fragment transaction after this method has executed, the application will throw an IllegalStateException and crash.
One thing you need to know is that onSaveInstanceState (Bundle) will be called before onStop ().
The good news is that after years of trying, Google is finally aware of the bug. In Android P, onStop () will be called before onSaveInstanceState (Bundle).
Conclusion:
It’s time to end this article.
While this article is neither short nor simple, I think it is simpler and more complete than most documents, including the official tutorial.
I know some seasoned professional developers will question my approach to the Activity lifecycle, and that’s okay. In fact, I look forward to your questions.
Keep in mind, though, that I’ve been using this scheme for several years. In my experience, the code written in this way is much cleaner and more concise than the logic you see handling the Activity lifecycle in many projects.
The ultimate validation of this approach comes from Google itself.
Starting with Nougat, Android supports multi-screen tasks. The approach I share with you in this article requires almost no tweaking. This basically confirms that it contains more insight into the Activity life cycle than the official documentation.
Additionally, Android P tweaks the order of calls between Activity onStop () and onSaveInstanceState (Bundle) methods make this method the safest for the use of Fragments.
I don’t think it’s a coincidence.