Previous  | Next  | Home

Lifecycle Methods


 

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.

 

Creating the Project in Android Studio

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.

 

Setting up the Resource Files

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.

 

Overriding the Lifecycle Methods

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.

 

What It Does

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.

  1. The first button will be used to launch a second activity NewActivity (which will just display a screen with some text on it).

  2. The second button calls the finish() method of Activity.

  3. The editable textfield will be used for input of a user name.

This is accomplished by

  1. Having the class implement the interface OnClickListener.

  2. Adding to onCreate() the code

    // 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);
    to identify and attach event listeners to the widgets.

  3. Implementing the onClick method of OnClickListener to sort out events and act accordingly:

    // 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.

 

Tracking Lifecycle Method Invocations

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):

  1. onCreate(Bundle savedInstanceState)

  2. onStart()

  3. onResume()

  4. onPause()

  5. onRestart()

  6. onStop()

  7. onDestroy()

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:

  1. A Toast, which displays the message as a transient floating window on the screen (with the duration controlled by the variable TL).

  2. Output to the LogCat stream, generated by the Log.i output statements.

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.

 

Using onPause() to Store User Data that Should Persist

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:
  1. Data may be stored as shared preferences, which are invoked through an interface SharedPreferences that stores data as name-value pairs in XML files on the device.

  2. Data may be stored on the device using a relational database, SQLite.

  3. Data may be written to flat files stored on the device, using the standard Java I/O streams such as FileOutputStream. We may distinguish two general modes of user storage:

  4. Data may be stored on and retrieved from a server over a network.
A more complete discussion of storage options may be found in the document Storage Options. For large amounts of data one of the latter three methods should be used, but shared preferences is a very convenient storage method for small things like user names or interface settings.

There are two steps to managing shared preferences after we define a SharedPreferences variable mprefs.

  1. In onCreate() we have inserted the lines

    // Set up shared prefs to hold username mPrefs = getSharedPreferences("LSprefs",0); userName = mPrefs.getString("user_name", ""); textfield.setText(userName);
    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.


    We then use the getString(String key, String defaultValue) method of SharedPreferences to retrieve a string value corresponding to the user-defined key user_name and assign it to the String variable userName


    Finally, we use the setText(CharSequence text) method that EditText inherits from TextField to display the value of userName in the EditText widget textfield.

  2. Next, in onPause() the lines

    // 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();
    have been inserted. In this snippet of code we

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.

 

Examining and Adding User Data to Android savedInstanceState Bundles

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

  1. onSaveInstanceState(Bundle outState), which is called to retrieve the per-instance state from an activity before it is killed. The Bundle specified by outState will be passed to both onCreate(Bundle) and onRestoreInstanceState(Bundle) if the application is re-initialized at a later time, so that either can be used to restore the user interface (normally this will be done by onCreate(Bundle)). For example, if activity B is launched in front of activity A, and at some point activity A is killed to reclaim resources, when the user returns to activity A the state of the user interface can be restored through onCreate(Bundle) or onRestoreInstanceState(Bundle).

  2. onRestoreInstanceState(Bundle savedInstanceState), which is called after onStart() when the activity is being re-initialized from a previously-saved state, specified by savedInstanceState. The default implementation performs a restore of any view state previously saved by onSaveInstanceState(Bundle). Since this same data Bundle is also passed to onCreate(), in typical implementations the UI state will be restored in onCreate() and it is then not necessary to call onRestoreInstanceState() also.

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).
  • To illustrate with a specific case, suppose a user launches activity B in front of activity A and then navigates back from B to A. That instance of B will never be restored so the system calls onPause() and onStop(), but not onSaveInstanceState(Bundle) on B.

  • As another example, if activity B is launched in front of activity A, onPause() will be called on A but the system may decide not to call onSaveInstanceState(Bundle) on A if it isn't killed during the lifetime of B because in that case the user interface for A will stay intact.
In the event that onSaveInstanceState(Bundle) is called, it will be called before onStop() but it could be called before or after onPause().

The default implementation of Android handles most of the UI per-instance state restoration automatically by

  1. Calling onSaveInstanceState(Bundle) on each view in the view hierarchy having an id.

  2. Saving the id of the view currently receiving focus.

  3. Automatically restoring these properties of the UI by passing the corresponding data Bundle to onRestoreInstanceState(Bundle) and onCreate(Bundle) when an app is reinitialized.

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

  1. Track onRestoreInstanceState(Bundle) and onSaveInstanceState(Bundle) invocations.

  2. Illustrate how to insert and retrieve private user data in the saved Bundles for these methods.

  3. Illustrate how to examine the contents of the data Bundles manipulated by these methods.

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.

 

Checking the Memory Usage

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.

  1. We don't instantiate this object directly (the ActivityManager class has no public constructors) but rather use the getSystemService(String name) method of Activity with argument ACTIVITY_SERVICE (a String constant of the Context class) to return an Object.

  2. This Object is then cast to an ActivityManager and assigned to the variable aMan.

  3. Then we instantiate an ActivityManager.MemoryInfo object called memInfo (ActivityManager.MemoryInfo is a nested class of ActivityManager).

  4. Finally, we invoke the getMemoryInfo (ActivityManager.MemoryInfo outInfo) method that aMan inherits from ActivityManager with outInfo = memInfo.

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.

 

Running It on a Device or Emulator

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.

 

Some Things to Try

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.

  1. Test that you can input a name in the textfield and it will persist until changed, even if you press the Call finish() button and start the app over again, or load any number of applications before returning to this one.

  2. Test starting the app, clicking on the top button to load the second activity, and then press the Back arrow on the phone to return to the first activity. When I tested this the resulting (filtered) logcat output was

    <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
    In this output we see behavior consistent with the general discussion above and in Application Lifecycles.



    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.


  3. After the above sequence, click the Call finish() button on the app, which should give

    <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
    where we see that the main activity is paused (permitting the variable userName to be written to shared preferences), and then stopped and destroyed.

  4. In Application Lifecycles we pointed out that an Exit button for an Android application (which normally would call finish()) is actually redundant because the Back button on the phone gives the same functionality for any application. If instead of pressing the Call finish() button in the above example one presses the Back button while on the main activity screen, the filtered logcat output is

    <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
    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().

  5. In the project Animal Sounds we shall demonstrate a simple example of supplying alternative resources for Android by defining different layouts for horizontal and vertical orientations of the phone. As documented more fully in Handling Runtime Changes, when Android implements a runtime configuration change (typical reasons are a change in orientation, changing availability of a physical keyboard, or a language change) the default behavior is that the currently running activity is killed and restarted in the new configuration (onDestroy() is called, followed by onCreate()). This is the only way that Android can be certain in the general case that the activity is properly initialized in the new configuration.

    When tested under rotation of the phone from vertical (portrait) to horizontal (landscape) mode with LifeCycleMethods executing the main activity, the filtered logcat output was

    <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
    Thus we see that when Android senses the orientation change of the phone it

    Then it restarts the main activity (now in landscape mode):

  6. The corresponding displays of the main activity are shown in the following figures.




    where we see that in the termination and restart of the application the user interface has been preserved, as well as the value of the entry in the textfield (and, as seen in the preceding output, the user data stored in the bundle saved by onSaveInstanceState(Bundle) has also persisted).


    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.


  7. Let's compare starting the application and then clicking the Back button with starting it and then clicking the Home button:

    <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
    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

    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
    since the task for this application is still running in the background and Android simply resumes it.

    However, suppose we click the Home button on the main activity, and then open and use various other apps while this app is in the background. Eventually Android will kill the task of the present app while it is in the background. In that case you should get logcat output like the following.

    <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
    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).)

    For typical applications, this re-creation of the app with saved data from the last visible instance state happens so quickly that it is difficult to distinguish this scenario from simply bringing an existing background task to the foreground. Thus the user perceives that the app was running all this time in the background, ready to leap immediately back to the foreground and resume interacting with the user. We know better, but only because of the careful monitoring we are doing of the lifecycle methods. The average user will never realize that in the second scenario the app was actually killed in the background and then created again when next needed. This is how Android manages to give a convincing multitasking experience on a device that has no swap space.

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


Previous  | Next  | Home