Android 4
25. Activity Life Cycle
This chapter
This chapter doesn't have any new stuff. It's a general review of some of the topics we've already dealt with. But I do not think it's a bad idea to go back and look things in a little bit relaxed mode.
So, in this chapter, we are going to review activity and life cycle via simple examples.
As we know, Android apps do not have a main method. Instead, apps have four types of components such as activities, services, content providers, and broadcast receivers.
An activity is a user interface concept. Users interacts with activities through views, in other words, GUI components. An activity usually represents a single screen in our application. A separate activity associate with each screen of an app. To create user interface screens we extend the Activity class, using Views to provide the UI and allow user interaction.
package com.bogotobogo.android.activities; import android.app.Activity; import android.os.Bundle; public class MyActivities extends Activity { // Called at the start of the full lifetime. @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } }
Here, we created a new activity by extending Activity. Within this new class we must define the user interface and implement the functionality.
We need to create a new activity for every screen we want to display. Usually this includes at least a primary interface screen that handles the main UI. This main UI is often supported by secondary activities.
The base activity class in the code above presents an empty screen that encapsulates the window display handling. An empty activity is almost useless, so what we do is to create the user interface with Views and layouts.
Views are the user interface controls that display data and provide user interaction.
public class MyActivities extends Activity { // Called at the start of the full lifetime. @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv = new TextView(this); setContentView(textView); } }
To assign a user interface to an activity, we call setContentView() from the
In most of the cases, we want to use a more complicated user interface. We can create a layout in code using layout View Groups, or we can use the standard Android convention of passing a resource ID for a layout defined in an external resource:
public class MyActivities extends Activity { // Called at the start of the full lifetime. @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
To use an activity in our application, we need to register it in the manifest. So, we add new <activity> tag within the <application> node of the manifest.
The <activity> tag includes attributes for metadata such as the label, icon, permissions, and themes which are used by the activity. An activity without a corresponding <activity> can't be displayed.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.bogotobogo.android.activities" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".MyActivities" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Inside the <activity> we can add <intent-filter> nodes what specify the intent our application will listen and react to. Each intent filter defines one or more actions and categories that our activity supports. For an activity to be available from the main application launcher, it must include an intent filter listening for the MAIN action and the LAUNCHER category.
<intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
Writing an application with a good understanding of the activity life cycle is critical to ensure that our application provides a seamless user experience and properly manages its resources.
Android application does not control its own process lifetimes. The Android run time manages the process of each application. While the run time handles the termination and management of an activity's process, the activity's state helps determine the priority of its parent application. The application priority, in turn, affects the likelihood that the run time will terminate it and the activities running within it.
The state of each activity is determined by its position on the activity stack. When a new activity starts, the current foreground screen is moved to the top of the stack. If the user navigates back using the Back button or the foreground activity is closed, the next activity on the stack moves up and becomes active.
An application's priority is influenced by its highest-priority activity. When the Android memory manager is deciding which application to terminate to free resources, it uses this stack to determine the priority of applications based on their activities.
As activities are created and destroyed they move in and out of the stack. They transition through 4 states:
- Active
When an activity is at the top of the stack it is the visible, focused, foreground activity that is receiving user input. Android will attempt to keep it alive at all cost, killing activities further down the stack as needed, to ensure that it has the resources it needs. When another activity becomes active, this one will be paused. - Paused
In some cases our activity will be visible but will not have focus. At this point it's paused. This state is reached if a transparent or not-full-screen activity is active in front of it. When paused, an activity is treated as if it were active. However, it doesn't receive user input events. In extreme cases, Android will kill a paused activity to recover resources for the active activity. When an activity becomes totally obscured, it is stopped. - Stopped
When an activity isn't visible, it stops. The activity will remain in memory, retaining all state information. However, it is now a candidate for termination when the system requires memory elsewhere. When an activity stopped, it's important to save data and the current UI state. Once an activity exited or closed, it becomes inactive. - Inactive
After an activity has been killed, and before it's been launched, it's inactive. Inactive activities have been removed from the activity stack and need to be restarted before they can be displayed and used.
State transitions are nondeterministic and are handled entirely by the Android memory manager. Android will start by closing applications that contain inactive activities, followed by those that are stopped. In extreme cases it will remove those that are paused.
To ensure that activities can react to state changes, Android provides several event handlers that are fired when an activity transitions through its full, visible, and active lifetime.
The code below shows the stubs for the state change method handlers available in an activity.
package com.bogotobogo.android.activities; import android.app.Activity; import android.os.Bundle; public class AndroidActivities extends Activity { // Called at the start of the full lifetime. @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Initialize activity. } // Called after onCreate has finished, use to restore UI state @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // Restore UI state from the savedInstanceState. // This bundle has also been passed to onCreate. } // Called before subsequent visible lifetimes // for an activity process. @Override public void onRestart(){ super.onRestart(); // Load changes knowing that the activity has already // been visible within this process. } // Called at the start of the visible lifetime. @Override public void onStart(){ super.onStart(); // Apply any required UI change now that the Activity is visible. } // Called at the start of the active lifetime. @Override public void onResume(){ super.onResume(); // Resume any paused UI updates, threads, or processes required // by the activity but suspended when it was inactive. } // Called to save UI state changes at the // end of the active lifecycle. @Override public void onSaveInstanceState(Bundle savedInstanceState) { // Save UI state changes to the savedInstanceState. // This bundle will be passed to onCreate if the process is // killed and restarted. super.onSaveInstanceState(savedInstanceState); } // Called at the end of the active lifetime. @Override public void onPause(){ // Suspend UI updates, threads, or CPU intensive processes // that don't need to be updated when the Activity isn't // the active foreground activity. super.onPause(); } // Called at the end of the visible lifetime. @Override public void onStop(){ // Suspend remaining UI updates, threads, or processing // that aren't required when the Activity isn't visible. // Persist all edits or state changes // as after this call the process is likely to be killed. super.onStop(); } // Called at the end of the full lifetime. @Override public void onDestroy(){ // Clean up any resources including ending threads, // closing database connections etc. super.onDestroy(); } }
Within an activity's full lifetime, between creation and destruction, it will go through one or more iterations of the active and visible lifetimes. Each transition will trigger the method handlers as shown in the previous code sample.
The full life time of our activity occurs between the first call to onCreate() and the final call to onDestroy(). It's possible, in some cases, for an activity's process terminates without the onDestroy() not being called.
Use the onCreate() method to initialize activity. Inflate the user interface, allocate references to class variables, bind data to controls, and create services and threads. The onCreate() method is passed a Bundle to restore the user interface to its previous state, either within the onCreate() method or by overriding onRestoreInstanceState().
It's recommended that we avoid the creation of short-term objects. Rapid creation and destruction of objects force additional garbage collection.
An activity's visible lifetimes are bound between calls to onStart() and onStop(). Between these calls our activity will be visible to the user, although it may not have focus and may be partially obscured. Activities are likely to go through several visible lifetimes during their full lifetime, as they move between the foreground and background. While it's unusual, Android run time will kill an activity during its visible lifetime without a call to onStop().
The onStop() method should be used to pause or stop animations, threads, sensor listeners, GPS lookups, timers, services, or other processes that are used exclusively to update the user interface. There's little value in consuming resources to update the UI when it isn't visible. Use the onStart() method to resume or restart these processes when the UI is visible again.
The onStart() method is called immediately prior to all but the first call to onStart(). Use it to implement special processing that we want done only when the activity restarts within its full lifetime.
The onStart()/onStop() methods are also used to register and unregister Broadcast Receivers that are being used exclusively to update the user interface.
The active lifetime starts with a call to onResume() and ends with a call to onPause().
An active activity is in the foreground and its receiving user input events. Our activity is likely to go through several active lifetimes before it's destroyed, as the active lifetime will end when a new activity is displayed, the device goes to sleep, or the activity loses focus. Try to keep code in the onPause() and onResume() methods relatively fast and lightweight to ensure that our application remains responsive when moving in and out of the foreground.
Immediately before onPause(), a call is made to onSaveInstanceState(). This method provides an opportunity to save the activity's UI state in a Bundle that will be passed to the onCreate() and onRestoreInstanceState() methods. Use onSaveInstanceState() to save the UI state to ensure that the activity can present the same UI when it next becomes active. We can safely assume that during the active lifetime onSaveInstanceState() and onPause() will be called before the process is terminated.
Most activity implementations will override at least the onPause() method to commit unsaved changes, as it marks the point beyond which an activity may be killed without warning. Depending on our application, we may also choose to suspend threads, processes, Broadcast Receivers while our activity is not in the foreground.
The onResume() method can be very lightweight. We will not need to reload the UI state here as this is handled by the onCreate() and onRestoreInstanceState() methods when required. Use onResume() to reregister any Broadcast Receivers or other processes we may have suspended in onPause().
Here is the list of the most useful activity classes.
- MapActivity
Encapsulates the resource handling required to support a MapView widget within an activity. - ListActivity
Wrapper class for activities that feature a ListView bound to a data source as the primary UI metaphor, and exposing event handlers for list item selection. - ExpandableActivity
Similar to the ListActivity but supporting an ExpandableListView bound to a data source as the primary UI metaphor, and exposing event handlers for list item selection. - TabActivity
Enables us to embed multiple activities or View within a single screen using a tab widget to switch among them.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization