- Android Application Overview
- Activity Lifecycle
- Multiple Activities
Activity Lifecycle
Each activity in an application goes through its own lifecycle. Once and only once when an activity is created, is the onCreate() function executed. If the activity exits, the onDestroy() function is executed. In between, various events can lead to the activity being in multiple different states, as illustrated in Figure 2.2. The next recipe provides an example of each of these functions.
Figure 2.2 Activity Lifecycle from http://developer.android.com/.
Recipe: Utilizing Other Lifecycle Functions
The following recipe provides a simple way to see the activity lifecycle in action. For illustration purposes, each overridden function is explicit and a Toast command is added to show on screen when the function is entered (more detail on the Toast widget is provided in Chapter 3). The activity is shown in Listing 2.6. Run it on an Android device and try various cases. In particular, note the following:
- Changing the screen orientation destroys and recreates the activity from scratch.
- Pressing the Home button pauses the activity, but does not destroy it.
- Pressing the Application icon might start a new instance of the activity, even if the old one was not destroyed.
- Letting the screen sleep pauses the activity and the screen awakening resumes it. (This is similar to taking an incoming phone call.)
Listing 2.6. src/com/cookbook/activity_lifecycle/ActivityLifecycle.java
package com.cookbook.activity_lifecycle; import android.app.Activity; import android.os.Bundle; import android.widget.Toast; public class ActivityLifecycle extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Toast.makeText(this, "onCreate", Toast.LENGTH_SHORT).show(); } @Override protected void onStart() { super.onStart(); Toast.makeText(this, "onStart", Toast.LENGTH_SHORT).show(); } @Override protected void onResume() { super.onResume(); Toast.makeText(this, "onResume", Toast.LENGTH_SHORT).show(); } @Override protected void onRestart() { super.onRestart(); Toast.makeText(this, "onRestart", Toast.LENGTH_SHORT).show(); } @Override protected void onPause() { Toast.makeText(this, "onPause", Toast.LENGTH_SHORT).show(); super.onPause(); } @Override protected void onStop() { Toast.makeText(this, "onStop", Toast.LENGTH_SHORT).show(); super.onStop(); } @Override protected void onDestroy() { Toast.makeText(this, "onDestroy", Toast.LENGTH_SHORT).show(); super.onDestroy(); } }
As seen here, various common actions by the user can cause the activity to be paused, killed, or even launch multiple versions of the application. Before moving on, it is worth mentioning two additional simple recipes that can control this behavior.
Recipe: Forcing Single Task Mode
As an application is navigated away from and launched again, it can lead to multiple instances of the activity on the device. Eventually the redundant instance of the activity is killed to free up memory, but in the meantime, it can lead to odd situations. To avoid these, the developer can control this behavior for each activity in the AndroidManifest.
To ensure only one instance of the activity runs on the device, specify the following in an activity element that has the MAIN and LAUNCHER intent filters:
android:launchMode="singleInstance"
This keeps a single instance of each activity in a task at all times. In addition, any child activity is launched as its own task. To constrain even further to only have a single task for all activities of an application, use the following:
android:launchMode="singleTask"
This allows the activities to share information easily as the same task.
In addition, it might be desirable to retain the task state, regardless of how a user navigates to the activity. For example, if a user leaves the application and relaunches it later, the default behavior often resets the task to its initial state. To ensure the user always returns to the task in its last state, specify the following in the activity element of the root activity of a task:
android:alwaysRetainTaskState="true"
Recipe: Forcing Screen Orientation
Any Android device with an accelerometer can determine which way is down. As the device is tilted from portrait to landscape mode, the default action is to rotate the application view accordingly. However, as seen from the "Other Lifecycle Functions" recipe, the activity is destroyed and restarted on screen orientation changes. When this happens, the current state of the activity might be lost, disrupting the user experience.
One option to handle screen orientation changes gracefully is to save state information before the change and restore information after the change. A simpler method that might be useful is to force the screen orientation to stay constant. For each activity in the AndroidManifest, the screenOrientation can be specified. For example, to specify that the activity always stays in portrait mode, the following can be added to the activity element:
android:screenOrientation="portrait"
Similarly, landscape mode can be specified using the following:
android:screenOrientation="landscape"
However, the previous still causes the activity to be destroyed and restarted when a hard keyboard is slid out. Therefore, a third method is possible: Tell the Android system that the application should handle orientation and keyboard slide-out events. This is done by adding the following attribute to the activity element:
android:configChanges="orientation|keyboardHidden"
This can be used alone or in combination with the screenOrientation attribute to specify the required behavior to the application.
Recipe: Saving and Restoring Activity Information
Whenever an activity is about to be killed, the onSaveInstanceState() function is called. Override this to save relevant information that should be retained. When the activity is then recreated, the onRestoreInstanceState() is called. Override this function to retrieve the saved information. This allows for a seamless user experience when an application undergoes lifecycle changes. Note that most UI states do not need to be managed because they are, by default, taken care of by the system.
This function is distinct from onPause(). For example, if another component is launched in front of the activity, the onPause() function is called. Later, if the activity is still paused when the OS needs to reclaim resources, it calls onSaveInstanceState() before killing the activity.
An example of saving and restoring the instance state consisting of a string and a float array is shown in Listing 2.7.
Listing 2.7. Example of onSaveInstanceState() and onRestoreInstanceState()
float[] localFloatArray = {3.14f, 2.718f, 0.577f}; String localUserName = "Euler"; @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); //save the relevant information outState.putString("name", localUserName); outState.putFloatArray("array", localFloatArray); } @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); //restore the relevant information localUserName = savedInstanceState.getString("name"); localFloatArray = savedInstanceState.getFloatArray("array"); }
Note that onCreate() also contains the Bundle savedInstanceState. In the case of an activity reinitializing after previously being shut down, the bundle saved in onSaveInstanceState() is also passed to onCreate(). In all cases, the saved bundle is passed to the onRestoreInstanceState() function, so it is more natural to utilize this to restore states.