There have been several messages recently on the Android Google Groups related to rotation — the act of moving the display from portrait to landscape or vice versa. In the case of the T-Mobile G1, rotation should occur as part of sliding open the keyboard to do text entry. Today, we’ll take a peek at how rotation and the activity lifecycle work in concert, and what that means for you, the intrepid Android developer.
Some developers have been mystified with their application’s behavior when the display is rotated. Specifically, initialization code in onCreate() appears to happen every time the screen rotates.
The good news is that these developers are not crazy. onCreate() is called every time the screen rotates…because on a screen rotation, the activity is destroyed and created anew with access to the appropriate set of resources.
To examine this, let’s build a test application containing two fairly trivial activities. One (“parent”) has a button that launches the other (“child”), whereas the second has a button that calls finish() on itself.
The layouts of the two are all but identical, varying only in the label of the button:
[sourcecode language=’xml’]
[/sourcecode]
The parent hooks up a button listener to launch the child activity. It also logs debug messages when onCreate(), onSaveInstanceState(), and onDestroy() are called:
[sourcecode language=’java’]
public class RotationDemoParent extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button btn=(Button)findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i=new Intent(RotationDemoParent.this, RotationDemoChild.class);
startActivity(i);
}
});
if (savedInstanceState==null) {
Log.d(“RotationDemo”, “got to Parent onCreate()”);
}
else {
Log.d(“RotationDemo”, “got to Parent onCreate() with a supplied state”);
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(“RotationDemo”, “got to Parent onDestroy()”);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(“RotationDemo”, “got to Parent onSaveInstanceState()”);
}
}
[/sourcecode]
The child is much the same, though its button listener calls finish() instead:
[sourcecode language=’java’]
public class RotationDemoChild extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.child);
Button btn=(Button)findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
finish();
}
});
if (savedInstanceState==null) {
Log.d(“RotationDemo”, “got to Child onCreate()”);
}
else {
Log.d(“RotationDemo”, “got to Child onCreate() with a supplied state”);
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(“RotationDemo”, “got to Child onDestroy()”);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(“RotationDemo”, “got to Child onSaveInstanceState()”);
}
}
[/sourcecode]
The activities are not much to look at, so we’ll eschew the screenshots and focus, instead, on what the debug messages tell us in various scenarios. To monitor the debug messages, you can use adb logcat or DDMS.
If you open the application and then close it, we get:
[sourcecode language=’java’]
10-14 08:28:41.915: DEBUG/RotationDemo(172): got to Parent onCreate()
10-14 08:28:44.494: DEBUG/RotationDemo(172): got to Parent onDestroy()
[/sourcecode]
If you open the application, click to launch the child activity, click the second activity’s button to close it, then close the first activity, we get:
[sourcecode language=’java’]
10-14 08:31:05.934: DEBUG/RotationDemo(172): got to Parent onCreate()
10-14 08:31:07.664: DEBUG/RotationDemo(172): got to Parent onSaveInstanceState()
10-14 08:31:07.744: DEBUG/RotationDemo(172): got to Child onCreate()
10-14 08:31:09.564: DEBUG/RotationDemo(172): got to Child onDestroy()
10-14 08:31:11.934: DEBUG/RotationDemo(172): got to Parent onDestroy()
[/sourcecode]
So far, that is all pretty much as expected.
Now, let’s open the application, rotate the emulator (via <Ctrl>-<F12> or the equivalent for your platform), and close the application. This time, we get:
[sourcecode language=’java’]
10-14 08:32:19.654: DEBUG/RotationDemo(172): got to Parent onCreate()
10-14 08:32:22.954: DEBUG/RotationDemo(172): got to Parent onSaveInstanceState()
10-14 08:32:22.954: DEBUG/RotationDemo(172): got to Parent onDestroy()
10-14 08:32:23.124: DEBUG/RotationDemo(172): got to Parent onCreate() with a supplied state
10-14 08:32:28.004: DEBUG/RotationDemo(172): got to Parent onDestroy()
[/sourcecode]
Notice how the mere act of rotating the display causes our activity to exit, then get restarted with the Bundle from onSaveInstanceState(). Also note that this occurs even though we have the exact same layout in each case — it is not like we have specified one layout for portrait and a different one for landscape.
Now, try opening the application, launching the second activity, rotating the display, closing the second activity, and closing the first activity:
[sourcecode language=’java’]
10-14 08:34:23.794: DEBUG/RotationDemo(172): got to Parent onCreate()
10-14 08:34:25.064: DEBUG/RotationDemo(172): got to Parent onSaveInstanceState()
10-14 08:34:25.134: DEBUG/RotationDemo(172): got to Child onCreate()
10-14 08:34:27.714: DEBUG/RotationDemo(172): got to Child onSaveInstanceState()
10-14 08:34:27.714: DEBUG/RotationDemo(172): got to Child onDestroy()
10-14 08:34:27.957: DEBUG/RotationDemo(172): got to Child onCreate() with a supplied state
10-14 08:34:30.364: DEBUG/RotationDemo(172): got to Parent onDestroy()
10-14 08:34:30.454: DEBUG/RotationDemo(172): got to Parent onCreate() with a supplied state
10-14 08:34:30.635: DEBUG/RotationDemo(172): got to Child onDestroy()
10-14 08:34:33.464: DEBUG/RotationDemo(172): got to Parent onDestroy()
[/sourcecode]
Here, we see that the parent activity is not destroyed until after the second activity has closed. In other words, the parent activity is oblivious to the rotation until it actually matters for its own operation.
You can see this even more clearly by opening the application, starting the second activity, rotating the display, rotating it back, then closing out of each activity:
[sourcecode language=’java’]
10-14 08:37:04.445: DEBUG/RotationDemo(172): got to Parent onCreate()
10-14 08:37:06.394: DEBUG/RotationDemo(172): got to Parent onSaveInstanceState()
10-14 08:37:06.475: DEBUG/RotationDemo(172): got to Child onCreate()
10-14 08:37:09.174: DEBUG/RotationDemo(172): got to Child onSaveInstanceState()
10-14 08:37:09.174: DEBUG/RotationDemo(172): got to Child onDestroy()
10-14 08:37:09.284: DEBUG/RotationDemo(172): got to Child onCreate() with a supplied state
10-14 08:37:15.284: DEBUG/RotationDemo(172): got to Child onSaveInstanceState()
10-14 08:37:15.284: DEBUG/RotationDemo(172): got to Child onDestroy()
10-14 08:37:15.425: DEBUG/RotationDemo(172): got to Child onCreate() with a supplied state
10-14 08:37:17.234: DEBUG/RotationDemo(172): got to Child onDestroy()
10-14 08:37:18.304: DEBUG/RotationDemo(172): got to Parent onDestroy()
[/sourcecode]
Notice that the parent only is destroyed when it is finally closed. Since the display was never rotated while it was active, it was unaffected by the rotation.
So, what does all of this mean?
- Be prepared to have your activity destroyed at any point. You should already be so prepared since interruptions and RAM starvation may require your activity to be killed off at unexpected times.
- Rotation destroys activities, not applications. When we rotated the screen in the second activity, the first activity was unaffected until it was to be displayed as rotated. Rotating the display, therefore, affects you at the activity level — it does not wipe out your entire stack of activities in one shot.