adb / Android / LogCat

Android Activity Lifecycle and Instance Management

The Problem

I recently had cause to take a very close look at how the Android OS and the Dalvik VM manages instances of Activity objects when starting and stopping your Activities.  I noticed on newer phones that class member values, and even static member values have the potential getting reset to their initialization state.  Whether or not that happens depends on how the cell phone manufacturer implemented the Android OS.  You will also notice when you run your application that the Activity lifecycle callbacks (e.g. onCreate, onResume, etc.) will be called  multiple times when an activity is displayed.

The only logical answer to this is some Android phones create instances of your Activity classes more than once when the Activity is put onto the UI stack.  And by created more than once, I mean it appears that either A) the entire VM managing the Activity is restarted, or B) the class loader is completely de-allocating your class definitions.

What I do know is this: the only thing you are guaranteed in Android activities is that Activity.onSaveInstanceState(Bundle) is called when Android needs you to save the member variables of your class. This recommendation applies to static class members as well.  Anyone not heeding this rule risks having things run just fine on one phone, and failing on another.

A Demonstration

For example, take this handy Activity class that sets up a couple constants useful for debugging:

package com.wordpress.rschilling;

public final class DebugConstants {
	public static final boolean DEBUG_LIFECYCLE = true;
	public static final String LOG_LIFECYCLE = "MyApp.LIFECYCLE";

	private DebugConstants() {
	}
}

And also this Activity class:

package com.wordpress.rschilling;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class LoggingActivity extends Activity {
	private boolean newinstance = true;
	private static boolean newstatic = true;

	public LoggingActivity() {
		super();
		if (DebugConstants.DEBUG_LIFECYCLE)
			Log.d(DebugConstants.LOG_LIFECYCLE, ": "
					+ this.getClass().getName() +
					" newstatic = " + newstatic);
		newstatic = false;
	}

	@Override
	protected void onPause() {
		super.onPause();
		if (DebugConstants.DEBUG_LIFECYCLE)
			Log.d(DebugConstants.LOG_LIFECYCLE, ": onPause "
					+ this.getClass().getName() +
					" newinstance = "
					+ newinstance);
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		if (DebugConstants.DEBUG_LIFECYCLE) {
			String nullBundle = savedInstanceState == null ? "null"
					: "not null";
			boolean reinitialized = savedInstanceState != null ? true : false;
			Log.d(DebugConstants.LOG_LIFECYCLE, ": onCreate(bundle = "
					+ nullBundle + ", reinitialized = " + reinitialized + ") "
					+ this.getClass().getName() + " newinstance = "
					+ newinstance);
		}
		newinstance = false;
	}

	@Override
	protected void onStart() {
		super.onStart();
		if (DebugConstants.DEBUG_LIFECYCLE)
			Log.d(DebugConstants.LOG_LIFECYCLE, ": onStart "
					+ this.getClass().getName() + " newinstance = "
					+ newinstance);
	}

	@Override
	protected void onRestart() {
		super.onRestart();
		if (DebugConstants.DEBUG_LIFECYCLE)
			Log.d(DebugConstants.LOG_LIFECYCLE, ": onRestart "
					+ this.getClass().getName() + " newinstance = "
					+ newinstance);
	}

	@Override
	protected void onResume() {
		super.onResume();
		if (DebugConstants.DEBUG_LIFECYCLE)
			Log.d(DebugConstants.LOG_LIFECYCLE, ": onResume "
					+ this.getClass().getName() + " newinstance = "
					+ newinstance);
	}

	@Override
	protected void onStop() {
		super.onStop();
		if (DebugConstants.DEBUG_LIFECYCLE)
			Log.d(DebugConstants.LOG_LIFECYCLE, ": onStop "
					+ this.getClass().getName() + " newinstance = "
					+ newinstance);
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		if (DebugConstants.DEBUG_LIFECYCLE)
			Log.d(DebugConstants.LOG_LIFECYCLE, ": onDestroy "
					+ this.getClass().getName() + " newinstance = "
					+ newinstance);
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		if (DebugConstants.DEBUG_LIFECYCLE)
			Log.d(DebugConstants.LOG_LIFECYCLE, ": onSaveInstanceState "
					+ this.getClass().getName() + " newinstance = "
					+ newinstance);
	}

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);
		if (DebugConstants.DEBUG_LIFECYCLE)
			Log.d(DebugConstants.LOG_LIFECYCLE, ": onLoadInstanceState "
					+ this.getClass().getName() + " newinstance = "
					+ newinstance);
	}
}

Run on the Emulator

Put those into a new Android Project in Eclipse and run the application in the debugger on the emulator. You’re may just see some output like this:

08-18 09:57:33.508: DEBUG/MyApp.LIFECYCLE(824): : <init>com.wordpress.rschilling.LoggingActivity newstatic = true
08-18 09:57:33.538: DEBUG/MyApp.LIFECYCLE(824): : onCreate(bundle = null, reinitialized = false) com.wordpress.rschilling.LoggingActivity newinstance = true
08-18 09:57:33.538: DEBUG/MyApp.LIFECYCLE(824): : onStart com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 09:57:33.538: DEBUG/MyApp.LIFECYCLE(824): : onResume com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 09:57:43.688: DEBUG/MyApp.LIFECYCLE(824): : onSaveInstanceState com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 09:57:43.738: DEBUG/MyApp.LIFECYCLE(824): : onPause com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 09:57:43.758: DEBUG/MyApp.LIFECYCLE(824): : onStop com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 09:57:43.758: DEBUG/MyApp.LIFECYCLE(824): : onDestroy com.wordpress.rschilling.LoggingActivity newinstance = false

<< ROTATE THE EMULATOR HERE – CTRL+F12, or KEYPAD_7, or FN+CTRL+F12 (on the MAC) >>

08-18 09:57:43.788: DEBUG/MyApp.LIFECYCLE(824): : <init>com.wordpress.rschilling.LoggingActivity newstatic = false
08-18 09:57:43.798: DEBUG/MyApp.LIFECYCLE(824): : onCreate(bundle = not null, reinitialized = true) com.wordpress.rschilling.LoggingActivity newinstance = true
08-18 09:57:43.818: DEBUG/MyApp.LIFECYCLE(824): : onStart com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 09:57:43.818: DEBUG/MyApp.LIFECYCLE(824): : onRestoreInstanceState com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 09:57:43.818: DEBUG/MyApp.LIFECYCLE(824): : onResume com.wordpress.rschilling.LoggingActivity newinstance = false

This output is from running just one instance of the application.  Notice the two lines that have “newstatic = true”, and remember that this is just one instance of the application running on a phone.  The static variables in the class were maintained by the emulator, but the class instance was wiped out and re-instantiated.  This means that the Dalvik virtual machine kept the class definition in memory, but the class instance got garbage collected.

Also notice that onLoadInstanceState(Bundle) was NOT called.  The bundle that was passed to onSaveInstanceState(Bundle) was send to onCreate().

Run on a Phone

If I run this on one of my Android  phones made by Samsung I get this:

08-18 10:00:49.633: DEBUG/MyApp.LIFECYCLE(4152): : <init>com.wordpress.rschilling.LoggingActivity newstatic = true
08-18 10:00:49.643: DEBUG/MyApp.LIFECYCLE(4152): : onCreate(bundle = null, reinitialized = false) com.wordpress.rschilling.LoggingActivity newinstance = true
08-18 10:00:49.643: DEBUG/MyApp.LIFECYCLE(4152): : onStart com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 10:00:49.643: DEBUG/MyApp.LIFECYCLE(4152): : onResume com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 10:00:53.173: DEBUG/MyApp.LIFECYCLE(4152): : onSaveInstanceState com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 10:00:53.173: DEBUG/MyApp.LIFECYCLE(4152): : onPause com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 10:00:53.173: DEBUG/MyApp.LIFECYCLE(4152): : onStop com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 10:00:53.173: DEBUG/MyApp.LIFECYCLE(4152): : onDestroy com.wordpress.rschilling.LoggingActivity newinstance = false

<< ROTATE THE EMULATOR HERE – CTRL+F12, or KEYPAD_7, or FN+CTRL+F12 (on the MAC) >>

08-18 10:00:53.203: DEBUG/MyApp.LIFECYCLE(4152): : <init>com.wordpress.rschilling.LoggingActivity newstatic = false
08-18 10:00:53.203: DEBUG/MyApp.LIFECYCLE(4152): : onCreate(bundle = not null, reinitialized = true) com.wordpress.rschilling.LoggingActivity newinstance = true
08-18 10:00:53.213: DEBUG/MyApp.LIFECYCLE(4152): : onStart com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 10:00:53.213: DEBUG/MyApp.LIFECYCLE(4152): : onRestoreInstanceState com.wordpress.rschilling.LoggingActivity newinstance = false
08-18 10:00:53.213: DEBUG/MyApp.LIFECYCLE(4152): : onResume com.wordpress.rschilling.LoggingActivity newinstance = false

Notice, once again the activity class is destroyed and then re-instantiated when the screen rotates. All non-static variable values are lost while static variables retain their value.  And, consistent with the documentation, the only thing that we are guaranteed is that onSaveInstanceState(Bundle) is called before the activity is destroyed (which happens just prior to the rotation).

Don’t Rely On Static Variables Anyway

Even though we can see that in this example, the static variable values were preserved, it’s important to not rely on them.  The Virtual Machine and the OS determine when class definitions are retained in the virtual machine.  If the operating system decides it needs more memory to display a new activity, it has the option of removing your entire Activity class from the virtual machine, and that means your static variables will be lost.

The documentation of the Activity.onSaveInstanceState(Bundle) class contains a clue:

This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state. For example, if activity B is launched in front of activity A, and at some point activity A is killed to reclaim resources, activity A will have a chance to save the current state of its user interface via this method so that when the user returns to activity A, the state of the user interface can be restored via onCreate(Bundle) or onRestoreInstanceState(Bundle).

 

The Conclusion

Make sure you follow the API’s expected behavior otherwise the values of your variables will be lost.  Here’s a short checklist to make sure you’ve got your bases covered:

  1. save the values of all your class variables, including static variables in onSaveInstanceState(Bundle) to the bundle.
  2. restore the variables from the bundle (if it’s not null) in onCreate(Bundle), and onRestoreInstanceState(Bundle).

One thought on “Android Activity Lifecycle and Instance Management

Leave a reply to Bachi Cancel reply