In this project we shall investigate more directly the Android lifecycle methods discussed under Application Lifecycles. Specifically, we shall
As we have emphasized in Application Lifecycles, understanding these issues is vital to writing responsive and user-friendly Android applications.
Following the general procedure in Creating a New Project, either choose Start a new Android Studio project from the Android Studio homepage, or from the Android Studio interface choose File > New > New Project. Fill out the fields in the resulting screens as follows,
Application Name:
LifeCycleMethods
Company Domain:< YourNamespace > Package Name: <YourNamespace> . lifecyclemethods Project Location: <ProjectPath> LifeCycleMethods Target Devices: Phone and Tablet; Min SDK API 15 Add an Activity: Empty Activity Activity Name: MainActivity (check the Generate Layout File box) Layout Name: activity_main |
where you should substitute your namespace for <YourNamespace> (com.lightcone in my case) and <ProjectPath> is the path to your directory for storing Android Studio Projects (/home/guidry/StudioProjects/ in my case). If you have chosen to use version control for your projects, go ahead and commit this project to version control.
Let's set up our resource files first. Edit app/res/values/strings.xml to define some strings that will be needed:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">LifeCycle Methods</string> <string name="action_settings">Settings</string> <string name="button1_label">Start New Activity</string> <string name="button2_label">Call finish ()</string> <string name="activityText">Second activity</string> <string name="edit_label">""</string> <string name="hint">(Enter User Name)</string> <string name="second_activity">Second Activity</string> </resources>
Next edit app/res/layout/activity_main.xml to define a layout with two buttons and one editable text field:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:id="@+id/button1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/button1_label" android:textSize="18sp" /> <Button android:id="@+id/button2" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/button2_label" android:textSize="18sp" /> <EditText android:text="@string/edit_label" android:id="@+id/EditText01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:hint="@string/hint" android:lines="1" android:inputType="text" /> </LinearLayout>
and create the XML file app/res/layout/newactivity.xml (right-click on app/res/layout and select New > XML > Layout XML File) with the content
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:text="@string/second_activity" android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content"> </TextView> </LinearLayout>
Now we will use two Java classes in conjunction with these resources to illustrate Android management of activity lifecycles.
Open the file app/java/<YourNamespace>.lifecyclemethods/MainActivity.java that was created with the project and edit it to read as follows.
package <YourNamespace>.lifecyclemethods; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import java.math.BigDecimal; import java.util.Set; import android.app.ActivityManager; import android.content.Intent; import android.content.SharedPreferences; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.EditText; import android.widget.Toast; import android.view.Menu; public class MainActivity extends AppCompatActivity implements OnClickListener { private static final int TL = Toast.LENGTH_SHORT; // Toast.LENGTH_LONG for longer private static final String AC = "Main Activity: "; private static final String TAG = "LIFECYCLES"; private static final double MB = 1048576; // bytes in megabyte private String message = ""; private SharedPreferences mPrefs; private EditText textfield; private String userName; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); message = "onCreate() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); // Add Click listeners for buttons View button1 = findViewById(R.id.button1); button1.setOnClickListener(this); View button2 = findViewById(R.id.button2); button2.setOnClickListener(this); // Identify EditText field textfield = (EditText) findViewById(R.id.EditText01); // Set up shared prefs to hold username mPrefs = getSharedPreferences("LSprefs",0); userName = mPrefs.getString("user_name", ""); textfield.setText(userName); // Explore the contents of the savedInstanceState Bundle restored by Android by obtaining // the set of keys in the Bundle and then displaying the corresponding content if(savedInstanceState != null){ Set<String> set = savedInstanceState.keySet(); Object sset[] = set.toArray(); for(int i=0; i<sset.length; i++){ String thisKey = sset[i].toString(); Log.i(TAG, " Restored Bundle savedInstanceState key "+i+" = "+thisKey); Log.i(TAG, " value = "+savedInstanceState.get(thisKey).toString()); } } else { Log.i(TAG, " Restored Bundle savedInstanceState is null"); } // Write memory usage when initialized. // See http://androidcommunity.com/forums/f4/how-to-intialise-activitymanager-5399/ ActivityManager aMan = (ActivityManager) this.getSystemService( ACTIVITY_SERVICE ); ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo (); aMan.getMemoryInfo( memInfo ); // Output memory information converted to megabytes Log.i(TAG, "Android Activity Manager:"); Log.i( TAG, " Available Memory: " + rounded((double)memInfo.availMem/MB, 1)+" MB"); Log.i( TAG, " Low Memory Threshhold: " + rounded((double)memInfo.threshold/MB, 1)+" MB"); Log.i( TAG, " Low Memory: " + memInfo.lowMemory ); } /** Lifecycle method: Called when the activity is becoming visible to user. */ @Override protected void onStart(){ super.onStart(); message = "onStart() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } /** Lifecycle method: Called when the activity begins interacting with the user. */ @Override protected void onResume(){ super.onResume(); message = "onResume() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } /** Lifecycle method: Called when the activity is being placed in the background. * Any user data that need to persist should be written to long-term storage here since * after onPause() returns there is no guarantee that further lifecycle methods will be * executed if the system decides to kill the app while it is in the background to reclaim * resources. We shall use shared preferences to illustrate writing to long-term storage. */ @Override protected void onPause(){ super.onPause(); message = "onPause() called (storing persistent user data)"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); // Write current userName to shared prefs so that it will persist even if the app is killed // after onPause() returns. userName = textfield.getText().toString(); SharedPreferences.Editor editor = mPrefs.edit(); editor.putString("user_name", userName); editor.commit(); message = "onPause() returning (user data stored)"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } /** Lifecycle method: Called after the activity has been stopped, prior to restarting. */ @Override protected void onRestart(){ super.onRestart(); message = "onRestart() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } /** Lifecycle method: Called when the activity is no longer visible to the user. */ @Override protected void onStop(){ super.onStop(); message = "onStop() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } /** Lifecycle method: The final call received before the activity is destroyed. */ @Override protected void onDestroy(){ super.onDestroy(); message = "onDestroy() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } // Note that this one is not a lifecycle method @Override protected void onSaveInstanceState (Bundle outState) { super.onSaveInstanceState(outState); // Example: insert user data into Bundle outState (illustrate: put current time in ms) outState.putLong("timeMillis", System.currentTimeMillis()); message = "onSaveInstanceState(Bundle outState) called."; message += " Bundle mappings = "+outState.size(); Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); // Explore the contents of the outState Bundle saved by Android by obtaining // the set of keys in the Bundle and then displaying the corresponding content. // This should show entries for both the user interface data saved automatically // by Android and the private user data inserted in the Bundle above. Set<String> set = outState.keySet(); Object sset[] = set.toArray(); for(int i=0; i<sset.length; i++){ String thisKey = sset[i].toString(); Log.i(TAG, " Saved Bundle outState key "+i+" = "+thisKey); Log.i(TAG, " value = "+outState.get(thisKey).toString()); } } // Note that this one is not a lifecycle method @Override protected void onRestoreInstanceState (Bundle inState) { super.onRestoreInstanceState(inState); message = "onRestoreInstanceState(Bundle inState) called."; message += " Bundle mappings = "+inState.size(); Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); // Explore the contents of the inState Bundle restored by Android by obtaining // the set of keys in the Bundle and then displaying the corresponding content if(inState != null){ Set<String> set = inState.keySet(); Object sset[] = set.toArray(); for(int i=0; i<sset.length; i++){ String thisKey = sset[i].toString(); Log.i(TAG, " Restored Bundle inState key "+i+" = "+thisKey); Log.i(TAG, " value = "+inState.get(thisKey).toString()); } } else { Log.i(TAG," Restored Bundle inState is null"); } } // Process button clicks @Override public void onClick(View v) { switch(v.getId()){ case R.id.button1: Intent j = new Intent(this, NewActivity.class); startActivity(j); break; case R.id.button2: message = AC+"Calling finish()"; Toast.makeText(this, message, TL).show(); Log.i(TAG, message); finish(); } } // Utility method to round a double to fixed number of decimal places. // http://groups.google.com/group/android-beginners/browse_thread/thread/c28370ba97c97d67 private static double rounded(double d, int decimalPlace){ BigDecimal bd = new BigDecimal(Double.toString(d)); bd = bd.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP); return bd.doubleValue(); } }
Then, create a class NewActivity by right-clicking on app/java/<YourNamespace>.lifecyclemethods, selecting New > Java Class, and in the resulting window entering the name NewActivity and clicking OK. Edit the resulting file app/java/<YourNamespace>.lifecyclemethods/NewActivity.java to read
package <YourNamespace>.lifecyclemethods; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.widget.Toast; public class NewActivity extends Activity { private static final int TL = Toast.LENGTH_SHORT; // Toast.LENGTH_LONG for longer private static final String TAG = "LIFECYCLES"; private static final String AC = "Second Activity: "; private String message = ""; /** Lifecycle method: Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.newactivity); message = "onCreate() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } /** Lifecycle method: Called when the activity is becoming visible to user. */ @Override protected void onStart(){ super.onStart(); message = "onStart() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } /** Lifecycle method: Called when the activity begins interacting with the user. */ @Override protected void onResume(){ super.onResume(); message = "onResume() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } /** Lifecycle method: Called when the activity is being placed in the background */ @Override protected void onPause(){ super.onPause(); message = "onPause() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } /** Lifecycle method: Called after the activity has been stopped, prior to restarting */ @Override protected void onRestart(){ super.onRestart(); message = "onRestart() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } /** Lifecycle method: Called when the activity is no longer visible to the user. */ @Override protected void onStop(){ super.onStop(); message = "onStop() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } /** Lifecycle method: The final call received before the activity is destroyed. */ @Override protected void onDestroy(){ super.onDestroy(); message = "onDestroy() called"; Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } // Note that this one is not a lifecycle method @Override protected void onSaveInstanceState (Bundle outState) { super.onSaveInstanceState(outState); message = "onSaveInstanceState(Bundle outState) called."; message += " Bundle mappings = "+outState.size(); Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } // Note that this one is not a lifecycle method @Override protected void onRestoreInstanceState (Bundle inState) { super.onRestoreInstanceState(inState); message = "onRestoreInstanceState(Bundle inState) called."; message += " Bundle mappings = "+inState.size(); Toast.makeText(this, AC+message, TL).show(); Log.i(TAG, AC+message); } }
Finally, since a new activity has been created a line must be added to register it in the manifest file. Open the file AndroidManifest.xml and add the line
<activity android:name=".NewActivity" android:label="NewActivity"> </activity>
inside the <application> ... </application> tag.
This project should now compile and run on an emulator or device (as illustrated below). Before doing that, let's discuss what it is going to do. The functionality of the code in these two activities is documented by extensive comments, but let's give a concise overview. Basically, we use the class MainActivity to put on the screen an initial activity with two Buttons and an EditText field.
This is accomplished by
to identify and attach event listeners to the widgets.// Add Click listeners for buttons View button1 = findViewById(R.id.button1); button1.setOnClickListener(this); View button2 = findViewById(R.id.button2); button2.setOnClickListener(this); // Identify EditText field textfield = (EditText) findViewById(R.id.EditText01);
// Process button clicks @Override public void onClick(View v) { switch(v.getId()){ case R.id.button1: Intent j = new Intent(this, NewActivity.class); startActivity(j); break; case R.id.button2: message = AC+"Calling finish()"; Toast.makeText(this, message, TL).show(); Log.i(TAG, message); finish(); } }
The preceding is all similar to manipulations illustrated in earlier projects such as WebView Demo, so we won't elaborate further. This gives us a caricature of the sort of things that a simple application might do (display widgets, respond to events, launch new activities, call finish() when done, ...). Our purpose now is to add some things to document the invocation of lifecycle methods as various actions are taken using this application, so that we gain a deeper understanding of Android lifecycle management.
The classes defined in MainActivity.java and NewActivity.java each extend Activity and override all seven of its lifecycle methods (see the diagram and table illustrating the lifecycle methods in Application Lifecycles):
In each of the overriden methods we first call through to the superclass and then implement two diagnostic outputs indicating that the particular method has been invoked:
The diagnostic messages allow us to see visually (on the screen and in the LogCat output) the invocation of various lifecycle callbacks by Android as we perform operations on the device or emulator.
You must call through to the superclass (for example, with super.onPause() in onPause()) for any Android lifecycle method that you override. If you fail to do so, a SuperNotCalledException will be thrown at runtime, leading to a Forced Close of your application. |
From the diagram and table of lifecycle methods in Application Lifecycles we may infer that user data that need to persist should be written to long-term storage when onPause() is invoked, since after onPause() returns there is no guarantee that further lifecycle methods will be executed if the system decides to kill the app while it is in the background to reclaim resources.
The lifecycle method onDestroy() is the last one called in the normal termination of an application. But, to quote directly from the onDestroy() documentation, "... do not count on this method being called as a place for saving data! ... There are situations where the system will simply kill the activity's hosting process without calling this method ..., so it should not be used to do things that are intended to remain around after the process goes away." |
Thus, in the activity MainActivity we also illustrate using onPause() to store user data. In this case, we store any username input in the editable textfield using shared preferences (see a further example of using shared preferences in the Options Menu implemented in the Map Example project).
There are several methods available for persistent data storage in Android programming:
|
There are two steps to managing shared preferences after we define a SharedPreferences variable mprefs.
The first line uses the method getSharedPreferences(String filename, int mode) that Activity inherits from the abstract class Context to return a SharedPreferences object and assign it to mprefs.// Set up shared prefs to hold username mPrefs = getSharedPreferences("LSprefs",0); userName = mPrefs.getString("user_name", ""); textfield.setText(userName);
have been inserted. In this snippet of code we// Write current userName to shared prefs so that it will persist even if the // app is killed after onPause() returns. userName = textfield.getText().toString(); SharedPreferences.Editor editor = mPrefs.edit(); editor.putString("user_name", userName); editor.commit();
This ensures that the current value of userName in textfield will be written to permanent storage when the application is moved to the background. Thus, it will not be lost even if Android kills the application process while it is in the background.
As discussed in Application Lifecycles, Android gives a multitasking experience on devices that have limited resources. One key to that capability is prompt restoration of applications that may have had their tasks killed while in a background state when they are restarted. Android does this by storing the state of the user interface in a data Bundle that can be used to quickly restore the user interface to its last visible state when the application is created again. The key methods in this regard are
Note that neither of these methods is a lifecycle method. They are often, but not always, called when lifecycle methods are called.
For example, the lifecycle method onPause() will always be called when an activity is placed in the background or is on its way to destruction, but this is not assured for onSaveInstanceState(Bundle).
|
The default implementation of Android handles most of the UI per-instance state restoration automatically by
However, the user can also insert private data in the form of name-value pairs into the Bundle that stores the UI state by overriding onRestoreInstanceState(Bundle). This would be of use, for example, in a game where the basic UI is saved automatically by Android but details not captured by each individual view that would be required to restore the last state of the game are not. Thus, in this project we also override the onRestoreInstanceState(Bundle) and onSaveInstanceState(Bundle) methods of both activities to
In both MainActivity.java and NewActivity.java the overriden onRestoreInstanceState(Bundle) and onSaveInstanceState(Bundle) methods have diagnostic Toast and LogCat outputs similar to those described for the lifecycle methods that should require no further explanation. Thus we concentrate on explaining the additional statements in these methods for MainActivity.java that illustrate inserting user data into the Bundle and querying the Bundle for its contents.
Notice in all of these implementations that when we override onRestoreInstanceState(Bundle) and onSaveInstanceState(Bundle) methods to save additional information not captured by each individual view, we must call through to the superclass in order to ensure that the default view information saved by Android is included in the Bundle. |
We shall illustrate storing user information in the data Bundle by storing the current system time in milliseconds each time onSaveInstanceState(Bundle) is invoked, and retrieving that stored time each time onRestoreInstanceState(Bundle) is invoked for the activity MainActivity. In onSaveInstanceState(Bundle) in LifeCycleMethods.java,
Next we explore the contents of the saved Bundle outState by obtaining the set of keys in the Bundle and then displaying the corresponding content. (This should show entries for both the user interface data saved automatically by Android and the private user data inserted in the Bundle above.)
Finally, we insert a similar for-loop in the method onRestoreInstanceState(Bundle) to explore the contents of the Bundle inState, and also in the method onCreate(Bundle) since both onRestoreInstanceState(Bundle) and onCreate(Bundle) should receive the same saved instance-state Bundles when an activity is reinitialized.
To guard against a nullPointerException, one should test for null on the input Bundles before trying to manipulate them, since they might not exist in particular circumstances. For example, if this is the first time the activity has been run there will be no saved Bundle for its UI state. |
As part of our information gathering we also examine the amount of memory in use during execution of the onCreate() method for the main activity. This can be done (in rather convoluted fashion) by obtaining an ActivityManager object.
Now the information that we seek is in the fields of the memInfo object:
We output these quantities to the logcat stream when onPause() of the main activity is executed, with two modifications:
There are at least three conventions used in different contexts for the number of bytes in a megabyte. Here we use the convention common in the context of internal computer memory that a megabyte (MB) is 220 = 1,048,576 bytes. |
Compile and execute the project on a device or emulator. You should obtain a display like the following figure
Where a transient Toast is being displayed indicating that the onResume() method of the main activity has just been called. If you consult the logcat output (see The Purr of the LogCat) you will find something like the following:
01-05 20:55:56.629 I/LIFECYCLES(20336): Main Activity: onCreate() called 01-05 20:55:56.644 I/LIFECYCLES(20336): Restored Bundle savedInstanceState is null 01-05 20:55:56.649 I/LIFECYCLES(20336): Android Activity Manager: 01-05 20:55:56.649 I/LIFECYCLES(20336): Available Memory: 64.4 MB 01-05 20:55:56.649 I/LIFECYCLES(20336): Low Memory Threshhold: 56.6 MB 01-05 20:55:56.654 I/LIFECYCLES(20336): Low Memory: false 01-05 20:55:56.659 I/LIFECYCLES(20336): Main Activity: onStart() called 01-05 20:55:56.664 I/LIFECYCLES(20336): Main Activity: onResume() called
Indicating that, as we would expect from the figure and table for the lifecycle sequence in Application Lifecycles, the application starts by executing the onCreate(), onStart(), and onResume() methods in sequence for the main activity. In addition, since this was the first time the application had been run after being installed on the phone, we see that the Bundle savedInstanceState input to onCreate() is null.
These lines were interspersed with many other system-generated lines in the logcat output.
For clarity, I have filtered on the tag "LIFECYCLES" to pull the relevant lines out of all the ones generated by the system. In this case I used the Linux command line to filter the output to only those lines containing the string "LIFECYCLES" by issuing
adb -d logcat -v time | grep LIFECYCLES
in a shell window.
Consult The Purr of the LogCat and the documentation for the
using logcat
for other filtering options.
|
You can try a number of things to test the preceding ideas and explore the Android lifecycle for a typical application. Here are some examples, all of which were obtained using this application on a Samsung Galaxy S phone running Android 2.1.
If you repeat the following examples you may not get verbatim the same output, since the exact sequence of lifecycle calls depends somewhat on the overall state of the device, and these examples were obtained using a very early version of Android. However, you should get output very similar to this that is consistent with the general principles of Android lifecycles discussed in this project and in Application Lifecycles. |
In this output we see behavior consistent with the general discussion above and in Application Lifecycles.<Application launched> 01-05 21:01:46.899 I/LIFECYCLES(20406): Main Activity: onCreate() called 01-05 21:01:46.924 I/LIFECYCLES(20406): Restored Bundle savedInstanceState is null 01-05 21:01:46.929 I/LIFECYCLES(20406): Android Activity Manager: 01-05 21:01:46.944 I/LIFECYCLES(20406): Available Memory: 65.2 MB 01-05 21:01:46.944 I/LIFECYCLES(20406): Low Memory Threshhold: 56.6 MB 01-05 21:01:46.944 I/LIFECYCLES(20406): Low Memory: false 01-05 21:01:46.949 I/LIFECYCLES(20406): Main Activity: onStart() called 01-05 21:01:46.954 I/LIFECYCLES(20406): Main Activity: onResume() called <Start New Activity button clicked> 01-05 21:01:58.154 I/LIFECYCLES(20406): Main Activity: onSaveInstanceState(Bundle outState) called. Bundle mappings = 2 01-05 21:01:58.154 I/LIFECYCLES(20406): Saved Bundle outState key 0 = timeMillis 01-05 21:01:58.154 I/LIFECYCLES(20406): value = 1294279318153 01-05 21:01:58.154 I/LIFECYCLES(20406): Saved Bundle outState key 1 = android:viewHierarchyState 01-05 21:01:58.154 I/LIFECYCLES(20406): value = Bundle[{android:focusedViewId=2131034114, android:views=android.util.SparseArray@47a88a98}] 01-05 21:01:58.179 I/LIFECYCLES(20406): Main Activity: onPause() called (storing persistent user data) 01-05 21:01:58.279 I/LIFECYCLES(20406): Main Activity: onPause() returning (user data stored) 01-05 21:01:58.299 I/LIFECYCLES(20406): Second Activity: onCreate() called 01-05 21:01:58.304 I/LIFECYCLES(20406): Second Activity: onStart() called 01-05 21:01:58.309 I/LIFECYCLES(20406): Second Activity: onResume() called 01-05 21:01:58.534 I/LIFECYCLES(20406): Main Activity: onStop() called <Back button clicked> 01-05 21:02:08.129 I/LIFECYCLES(20406): Second Activity: onPause() called 01-05 21:02:08.174 I/LIFECYCLES(20406): Main Activity: onRestart() called 01-05 21:02:08.184 I/LIFECYCLES(20406): Main Activity: onStart() called 01-05 21:02:08.199 I/LIFECYCLES(20406): Main Activity: onResume() called 01-05 21:02:08.444 I/LIFECYCLES(20406): Second Activity: onStop() called 01-05 21:02:08.454 I/LIFECYCLES(20406): Second Activity: onDestroy() called
A Bundle can hold many data types in its name-value pair entries; here we see that one possibility is another Bundle. |
This is a specific example of a scenario discussed earlier where onPause() is called but not onSaveInstanceState(): activity B was launched in front of activity A; then when we navigated back to activity A, activity B was paused, stopped, and destroyed without onSaveInstanceState() being called on it. |
where we see that the main activity is paused (permitting the variable userName to be written to shared preferences), and then stopped and destroyed.<Call finish() button pressed> 01-05 21:09:08.394 I/LIFECYCLES(20406): Main Activity: Calling finish() 01-05 21:09:08.414 I/LIFECYCLES(20406): Main Activity: onPause() called (storing persistent user data) 01-05 21:09:08.884 I/LIFECYCLES(20406): Main Activity: onPause() returning (user data stored) 01-05 21:09:09.564 I/LIFECYCLES(20406): Main Activity: onStop() called 01-05 21:09:09.564 I/LIFECYCLES(20406): Main Activity: onDestroy() called
This is exactly the same lifecycle-method sequence as obtained by pressing Call finish(), confirming the assertion that pressing the Back button on an Android device has the same functionality as executing finish().<Back button pressed> 01-05 21:12:28.109 I/LIFECYCLES(20476): Main Activity: onPause() called (storing persistent user data) 01-05 21:12:28.179 I/LIFECYCLES(20476): Main Activity: onPause() returning (user data stored) 01-05 21:12:28.429 I/LIFECYCLES(20476): Main Activity: onStop() called 01-05 21:12:28.429 I/LIFECYCLES(20476): Main Activity: onDestroy() called
Thus we see that when Android senses the orientation change of the phone it<Application started with phone in portrait mode> 01-05 21:14:35.139 I/LIFECYCLES(20476): Main Activity: onCreate() called 01-05 21:14:35.144 I/LIFECYCLES(20476): Restored Bundle savedInstanceState is null 01-05 21:14:35.144 I/LIFECYCLES(20476): Android Activity Manager: 01-05 21:14:35.144 I/LIFECYCLES(20476): Available Memory: 57.1 MB 01-05 21:14:35.149 I/LIFECYCLES(20476): Low Memory Threshhold: 56.6 MB 01-05 21:14:35.149 I/LIFECYCLES(20476): Low Memory: true 01-05 21:14:35.149 I/LIFECYCLES(20476): Main Activity: onStart() called 01-05 21:14:35.154 I/LIFECYCLES(20476): Main Activity: onResume() called <Phone rotated to landscape mode> 01-05 21:15:01.549 I/LIFECYCLES(20476): Main Activity: onSaveInstanceState(Bundle outState) called. Bundle mappings = 2 01-05 21:15:01.569 I/LIFECYCLES(20476): Saved Bundle outState key 0 = timeMillis 01-05 21:15:01.569 I/LIFECYCLES(20476): value = 1294280101511 01-05 21:15:01.569 I/LIFECYCLES(20476): Saved Bundle outState key 1 = android:viewHierarchyState 01-05 21:15:01.574 I/LIFECYCLES(20476): value = Bundle[{android:focusedViewId=2131034114, android:views=android.util.SparseArray@47aa7108}] 01-05 21:15:01.589 I/LIFECYCLES(20476): Main Activity: onPause() called (storing persistent user data) 01-05 21:15:01.659 I/LIFECYCLES(20476): Main Activity: onPause() returning (user data stored) 01-05 21:15:01.674 I/LIFECYCLES(20476): Main Activity: onStop() called 01-05 21:15:01.674 I/LIFECYCLES(20476): Main Activity: onDestroy() called 01-05 21:15:01.714 I/LIFECYCLES(20476): Main Activity: onCreate() called 01-05 21:15:01.719 I/LIFECYCLES(20476): Restored Bundle savedInstanceState key 0 = timeMillis 01-05 21:15:01.719 I/LIFECYCLES(20476): value = 1294280101511 01-05 21:15:01.719 I/LIFECYCLES(20476): Restored Bundle savedInstanceState key 1 = android:viewHierarchyState 01-05 21:15:01.719 I/LIFECYCLES(20476): value = Bundle[{android:focusedViewId=2131034114, android:views=android.util.SparseArray@47aa7108}] 01-05 21:15:01.724 I/LIFECYCLES(20476): Android Activity Manager: 01-05 21:15:01.724 I/LIFECYCLES(20476): Available Memory: 61.2 MB 01-05 21:15:01.724 I/LIFECYCLES(20476): Low Memory Threshhold: 56.6 MB 01-05 21:15:01.724 I/LIFECYCLES(20476): Low Memory: false 01-05 21:15:01.729 I/LIFECYCLES(20476): Main Activity: onStart() called 01-05 21:15:01.734 I/LIFECYCLES(20476): Main Activity: onRestoreInstanceState(Bundle inState) called. Bundle mappings = 2 01-05 21:15:01.734 I/LIFECYCLES(20476): Restored Bundle inState key 0 = timeMillis 01-05 21:15:01.734 I/LIFECYCLES(20476): value = 1294280101511 01-05 21:15:01.734 I/LIFECYCLES(20476): Restored Bundle inState key 1 = android:viewHierarchyState 01-05 21:15:01.744 I/LIFECYCLES(20476): value = Bundle[{android:focusedViewId=2131034114, android:views=android.util.SparseArray@47aa7108}] 01-05 21:15:01.749 I/LIFECYCLES(20476): Main Activity: onResume() called
In the project Animal Sounds we will demonstrate how to supply alternative layout resource files that Android uses automatically when switching from vertical to horizontal mode. In the present example we have not done that so Android defaults to using the file activity_main.xml to configure the screen in both modes. For example, note that the buttons expand horizontally to fill the screen in both orientations, corresponding to the attribute android:layout_width="fill_parent" for the buttons. |
The obvious difference is that clicking the Back button terminates the application, calling onDestroy() on the main activity, but clicking the Home button causes a restart bundle to be saved and data to be saved in shared preferences as onPause() is executed, and then onStop() is executed, but onDestroy() is not executed immediately. If we navigate back to this application soon, without doing a lot of other things on the phone, the likely action is<Application started> 01-05 22:09:42.479 I/LIFECYCLES(20780): Main Activity: onCreate() called 01-05 22:09:42.524 I/LIFECYCLES(20780): Restored Bundle savedInstanceState is null 01-05 22:09:42.524 I/LIFECYCLES(20780): Android Activity Manager: 01-05 22:09:42.529 I/LIFECYCLES(20780): Available Memory: 62.9 MB 01-05 22:09:42.529 I/LIFECYCLES(20780): Low Memory Threshhold: 56.6 MB 01-05 22:09:42.529 I/LIFECYCLES(20780): Low Memory: false 01-05 22:09:42.534 I/LIFECYCLES(20780): Main Activity: onStart() called 01-05 22:09:42.539 I/LIFECYCLES(20780): Main Activity: onResume() called <Back button pressed> 01-05 22:09:55.449 I/LIFECYCLES(20780): Main Activity: onPause() called (storing persistent user data) 01-05 22:09:55.934 I/LIFECYCLES(20780): Main Activity: onPause() returning (user data stored) 01-05 22:09:56.289 I/LIFECYCLES(20780): Main Activity: onStop() called 01-05 22:09:56.294 I/LIFECYCLES(20780): Main Activity: onDestroy() called <Application started> 01-05 22:10:25.169 I/LIFECYCLES(20780): Main Activity: onCreate() called 01-05 22:10:25.174 I/LIFECYCLES(20780): Restored Bundle savedInstanceState is null 01-05 22:10:25.174 I/LIFECYCLES(20780): Android Activity Manager: 01-05 22:10:25.179 I/LIFECYCLES(20780): Available Memory: 64.9 MB 01-05 22:10:25.179 I/LIFECYCLES(20780): Low Memory Threshhold: 56.6 MB 01-05 22:10:25.184 I/LIFECYCLES(20780): Low Memory: false 01-05 22:10:25.184 I/LIFECYCLES(20780): Main Activity: onStart() called 01-05 22:10:25.189 I/LIFECYCLES(20780): Main Activity: onResume() called <Home button pressed> 01-05 22:10:37.444 I/LIFECYCLES(20780): Main Activity: onSaveInstanceState(Bundle outState) called. Bundle mappings = 2 01-05 22:10:37.444 I/LIFECYCLES(20780): Saved Bundle outState key 0 = timeMillis 01-05 22:10:37.444 I/LIFECYCLES(20780): value = 1294283437425 01-05 22:10:37.449 I/LIFECYCLES(20780): Saved Bundle outState key 1 = android:viewHierarchyState 01-05 22:10:37.449 I/LIFECYCLES(20780): value = Bundle[{android:focusedViewId=2131034114, android:views=android.util.SparseArray@479f6e68}] 01-05 22:10:37.454 I/LIFECYCLES(20780): Main Activity: onPause() called (storing persistent user data) 01-05 22:10:37.524 I/LIFECYCLES(20780): Main Activity: onPause() returning (user data stored) 01-05 22:10:38.104 I/LIFECYCLES(20780): Main Activity: onStop() called
since the task for this application is still running in the background and Android simply resumes it.01-05 23:39:51.733 I/LIFECYCLES(22302): Main Activity: onRestart() called 01-05 23:39:51.738 I/LIFECYCLES(22302): Main Activity: onStart() called 01-05 23:39:51.743 I/LIFECYCLES(22302): Main Activity: onResume() called
We see from this sequence that Android has killed the process for our app while it is in the background, because with all the other apps that we opened it eventually decided that it needed to reclaim the resources. It did so without ever calling onDestroy() on our app, since the Android resource management model is to kill background tasks that it deems non-essential without warning if it senses a low-resource condition. (This is why onDestroy() is not the right place to save data that need to persist: it may never be called.) Because the original task was killed, the system has to invoke onCreate() to create the app again. However, it reads back the stored data bundle while executing onCreate(), which restores the user interface to its last visible state and restores the value of our user-defined data corresponding to the key "timeMillis". (The value of "timeMillis" that was stored at time 23:58:02 by onSaveInstanceState(outState) is the same value that is read back in seven minutes later at time 00:05:02 in onCreate(savedInstanceState).)<Home button clicked while in main activity, sending app to background.> 01-05 23:58:02.305 I/LIFECYCLES(22443): Main Activity: onSaveInstanceState(Bundle outState) called. Bundle mappings = 2 01-05 23:58:02.305 I/LIFECYCLES(22443): Saved Bundle outState key 0 = timeMillis 01-05 23:58:02.310 I/LIFECYCLES(22443): value = 1294289882298 01-05 23:58:02.310 I/LIFECYCLES(22443): Saved Bundle outState key 1 = android:viewHierarchyState 01-05 23:58:02.310 I/LIFECYCLES(22443): value = Bundle[{android:focusedViewId=2131034114, android:views=android.util.SparseArray@47b60ce0}] 01-05 23:58:02.315 I/LIFECYCLES(22443): Main Activity: onPause() called (storing persistent user data) 01-05 23:58:02.380 I/LIFECYCLES(22443): Main Activity: onPause() returning (user data stored) 01-05 23:58:02.780 I/LIFECYCLES(22443): Main Activity: onStop() called <While app is in background, do various other things on the phone (open and use other apps, etc.)> <Monitor the app's task while doing this. Eventually its task will disappear. Then navigate back to the app.> 01-06 00:05:02.079 I/LIFECYCLES(22756): Main Activity: onCreate() called 01-06 00:05:02.094 I/LIFECYCLES(22756): Restored Bundle savedInstanceState key 0 = timeMillis 01-06 00:05:02.094 I/LIFECYCLES(22756): value = 1294289882298 01-06 00:05:02.094 I/LIFECYCLES(22756): Restored Bundle savedInstanceState key 1 = android:viewHierarchyState 01-06 00:05:02.094 I/LIFECYCLES(22756): value = Bundle[mParcelledData.dataSize=460] 01-06 00:05:02.094 I/LIFECYCLES(22756): Android Activity Manager: 01-06 00:05:02.099 I/LIFECYCLES(22756): Available Memory: 110.1 MB 01-06 00:05:02.099 I/LIFECYCLES(22756): Low Memory Threshhold: 56.6 MB 01-06 00:05:02.099 I/LIFECYCLES(22756): Low Memory: false 01-06 00:05:02.104 I/LIFECYCLES(22756): Main Activity: onStart() called 01-06 00:05:02.114 I/LIFECYCLES(22756): Main Activity: onRestoreInstanceState(Bundle inState) called. Bundle mappings = 2 01-06 00:05:02.114 I/LIFECYCLES(22756): Restored Bundle inState key 0 = timeMillis 01-06 00:05:02.114 I/LIFECYCLES(22756): value = 1294289882298 01-06 00:05:02.114 I/LIFECYCLES(22756): Restored Bundle inState key 1 = android:viewHierarchyState 01-06 00:05:02.114 I/LIFECYCLES(22756): value = Bundle[{android:views=android.util.SparseArray@479d0cb8, android:focusedViewId=2131034114}] 01-06 00:05:02.119 I/LIFECYCLES(22756): Main Activity: onResume() called
In this example, run on a high-end phone (for the year ~2010; modern high-end phones are much more capable!), I typically had to open and use 5-10 other applications, including large ones like the Angry Birds game, before Android decided to kill the task of our app running in the background. |
These examples, and experiments that you can do on your own with this app, should give you a good feeling for how lifecycle methods function in Android.
The complete project for the application described above is archived at the link LifeCycleMethods. Instructions for installing it in Android Studio may be found in Packages for All Projects. |
Last modified: July 25, 2016