Chapter 1
In This Chapter
Launching an activity
Going through an activity’s lifecycle
Getting information from an activity
On a desktop computer, everything starts with a window. Open a window, and run a word processor. Open another window, and read your email. Move a window, minimize a window, resize a window. It’s a very familiar story.
But mobile devices aren’t desktop computers. A smartphone has a relatively small screen, and if by chance you could open several windows at once, the phone’s processor would fall over from exhaustion. On a mobile phone, the “window” metaphor would lead to nothing but trouble.
Tablet devices have larger screens and better processors than their telephone cousins. You can probably squeeze a few windows on a tablet screen, but the power that you would allocate to window-handling could be put to better use.
So where does that leave you? The earliest computers had no windows and no multitasking. You can’t have that. Without some kind of multitasking, “smartphones” wouldn’t be smart.
Android’s first and foremost answer to all windowing questions is the activity. In other chapters, I refer to an activity as “one screenful of components.” I liken activities to online forms, such as “a form for entering information to make a purchase on a website.” I write, “Unlike most online forms, Android activities don’t necessarily have text boxes — places for the user to type credit card numbers and such. But Android activities have a lot in common with online forms.” I love quoting myself.
Here’s what the official Android docs say about an activity:
An activity is a single, focused thing that the user can do. Almost all activities interact with the user, so the Activity class takes care of creating a window for you in which you can place your UI with setContentView(View)
. While activities are often presented to the user as full-screen windows …
The android.app.Activity
class’s code is a complete, official definition describing what an activity is and what an activity isn’t. But from an app designer’s point of view, no formal definition of activity paints the complete picture. So maybe the way to describe an activity is behaviorally. Here’s my informal description:
At some point during the run of an app, an app designer fills up the screen with stuff. At that point, the designer thinks, “I have to move all this stuff out of the way so the user can deal sensibly with whatever has become most important.” So the designer creates a new screen layout, codes the layout’s behavior, and refers to the whole business (the layout and its behavior) as a new activity.
At that (newer) point, the designer has two different activities — the original activity that filled up the screen with stuff and the new activity that deals with whatever has become most important. On a smaller device (a smartphone as opposed to a tablet), each activity fills the entire screen. The original activity invokes the new activity and then the new activity covers the original activity.
Under normal circumstances, the two activities form part of a stack — a first in, last out structure. Imagine that Activity A invokes Activity B, which in turn invokes Activity C. Then the activities A, B, and C form a stack, with Activity C being on top of the stack (and visible to the user). When the user presses the Back button, Activity C pops off the stack to reveal Activity B. When the user presses the Back button again, Activity B pops off the stack to reveal Activity A.
A stack of Android activities is called a task. So now you have apps, activities, and tasks. Unfortunately, these words have different meanings for Android developers than they have for the rest of the world.
An application is a collection of things meant to accomplish a particular user goal. Some of the things belonging to an app are activities.
Each application runs in its own Android Linux process, with its own user ID. This is one of Android’s security strengths. Separate processes don’t share memory. So in effect, each application is sandboxed from the rest of the system, with very narrowly defined (tightly guarded) paths of communication between one application and another. If an application does something wrong (either maliciously or unintentionally), the chance of that wrongdoing affecting the rest of the system is limited. As a developer, you create an application using the XML <application>
element in the app’s AndroidManifest.xml
file.
An activity can (and frequently does) invoke activities belonging to other apps. (For example, an email message might contain a link. So an email app’s activity might invoke a web browser app’s activity.) That means that a particular task might contain activities from several applications. (See Figure 1-1.)
The scenario often works this way:
The user starts an app. (Call it Application 1.)
Android creates a new process for the app, creates an instance of the app’s main activity, and puts the main activity onto a brand-new task stack. (Call it Task 1.)
From the app’s main activity, the user invokes another activity (say, a secondary activity belonging to the same app).
Android creates a new instance of the secondary activity. Android pushes the secondary activity onto the task stack. (See Figure 1-2.) The device’s screen displays only the secondary activity. (Think of the app’s main activity as being hidden underneath the secondary activity. Call the main activity Activity 1; call the secondary activity Activity 2.)
The user presses Home.
Android moves Task 1 off the screen and displays the Home screen, as shown in Figure 1-3.
The user starts a second app. (How about calling it Application 2?)
With Task 1 still waiting in the wings, Android creates a second task (Task 2) with the second app’s main activity.
The user presses Home again and presses the icon for Application 1.
See Figure 1-4. Android displays the top of the Task 1 stack. Activity 2 is still at the top of Task 1. So the user sees Activity 2. Happily, Activity 2 is in the same state as it was when the user first pressed Home. Text fields still have whatever text the user previously entered, and so on.
The user presses the Back button.
Android pops Activity 2 off the Task 1 stack and destroys this instance of Activity 2. The user sees Activity 1, which is in the same state as it was immediately before Android covered up Activity 1 with Activity 2.
From Activity 1, the user again invokes the secondary activity belonging to Application 1.
Android creates a brand-new instance of the secondary activity. Android pushes the secondary activity onto the task stack. The device’s screen displays only the secondary activity. This new instance is not in the same state that Activity 2 was in when Activity 2 was destroyed. This new instance is initialized with new values (which is normal for brand-new objects).
The Android programming model is based on the use of scarce resources. Compared to a desktop or laptop computer, a smartphone has a small screen, limited memory, and a wimpy processor. With that in mind, the original creators of Android focused on reuse.
Imagine that my app includes a link to my website. When the user clicks the link, Android opens a web browser. But which browser does Android open? Android comes with its own browser (a close cousin of Google’s Chrome browser). But the user might have also installed Firefox for Android, Opera Mini, or any number of other web browsers.
In the Windows and Mac operating systems, the choice of browser depends on filename extensions and protocol associations. For example, if I double-click the icon for a file named something
.html
, the Chrome browser opens. But if I double-click the icon for a somethingElse
.torrent
file, the Opera browser opens.
In Android, the developer invokes a browser by issuing an intent.
In Android development, an intent is like an open-ended method call. Instead of coding something like
firefox("http://www.google.com");
or
android_built_in_browser("http://allmycode.com/android");
you code the following:
String url = "http://allmycode.com/android";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
startActivity(intent);
In this example, calling startActivity(intent)
is like throwing an I-want-to-browse ball into the air and expecting another app’s activity to catch it. Another app announces its intentions to catch the I-want-to-browse ball by putting an element of the following kind in the app’s AndroidManifest.xml
file:
<activity android:name=".Catcher"
android:label="Catcher">
<intent-filter>
<action
android:name="android.intent.action.VIEW" />
<category
android:name="android.intent.category.DEFAULT" />
<category
android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
</intent-filter>
</activity>
Again, I quote Android’s official documentation:
An intent is an abstract description of an operation to be performed. It can be used withstartActivity
to launch anActivity
… . An Intent provides a facility for performing late runtime binding between the code in different applications. Its most significant use is in the launching of activities, where it can be thought of as the glue between activities. It is basically a passive data structure holding an abstract description of an action to be performed.
In truth, Android has two kinds of intents — implicit and explicit intents.
The kind of intent that I describe in the previous paragraphs (to start any activity claiming to be a web browser) is an implicit intent. An implicit intent doesn’t call for a particular activity to be launched. Instead, an implicit intent names an action to be taken, along with other information required to fulfill the intent.
The intent
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
is an implicit intent.
I cover implicit intents in Chapter 2 of this minibook.
An explicit intent actually names an activity class whose instance is to be launched.
In this chapter’s examples, I use explicit intents to launch activities. So the next section covers explicit intents.
To use an explicit intent, you can write something like the code in Listing 1-1.
Listing 1-1: Calling Your Own App’s Activity Using an Explicit Intent
package my.pack;
import android.app.Activity;
import android.os.Bundle;
public class SomeActivity extends Activity {
// … code of some sort belongs here
Intent intent = new Intent();
intent.setClassName("my.pack", "my.pack.OtherActivity");
startActivity(intent);
}
In Listing 1-1, the intent’s setClassName
method takes two String
parameters. The first parameter is the name of the package containing the target activity, and the second parameter is the activity’s fully qualified class name. So in Listing 1-1, the call to setClassName
has two strings containing "my.pack"
. (There may be a reasonable way to avoid repetition, where you’d write something like setClassName("my.pack", "OtherActivity")
. But if there is one, no one’s told me about it.)
To invoke another app’s activity, you can write something like the code in Listing 1-2.
Listing 1-2: Calling Another App’s Activity Using an Explicit Intent
package my.pack;
import android.app.Activity;
import android.os.Bundle;
public class SomeActivity extends Activity {
// … code of some sort belongs here
intent = new Intent();
intent.setClassName("other.pack",
"other.pack.OtherAppActivity");
startActivity(intent);
}
Listing 1-2 is almost exactly like Listing 1-1. The only difference is that in Listing 1-2, the two activities (the invoking and the invoked activities) belong to two different applications. Different applications means different packages. So Listing 1-1 has "my.pack"
, and Listing 1-2 has "other.pack"
.
Another way to start a specific activity is with a context. A context is an “interface to global information about an application environment.” (Again, I’m quoting the Android docs.) Informally, a context is the background information that you might want to know about the things that are actually happening. For an Android app, the Context
Java object might include the app’s package name, the theme, the wallpaper, the names of files associated with the app, and pointers to location services, to user account services, and to other info. All this stuff is available programmatically by way of a Java Context
object.
An Android activity runs in a certain context. That makes sense. But here’s an idea that’s always been difficult for me to embrace: An activity is a context. It’s a context for two reasons (one being technical; the other being somewhat intuitive):
android.app.Activity
is a subclass of android.content.Context
.In Listing 1-3, an activity calls another activity within the same application.
Listing 1-3: Calling Your Own App’s Activity Using an Explicit Intent with a Context
package my.pack;
import android.app.Activity;
import android.os.Bundle;
public class SomeActivity extends Activity {
// … code of some sort belongs here
Intent intent =
new Intent(this, OtherActivity.class);
startActivity(intent);
}
In Listing 1-3, the last two statements are really saying, “With this
activity’s own context, start running an instance of OtherActivity
.” (If all goes well, the class OtherActivity
extends Android’s Activity
class, and you’re good to go.)
In Listing 1-3, the Intent
class’s constructor takes two parameters — a context and a Java class. The word this
represents the enclosing SomeActivity
instance. That’s good, because the constructor’s first parameter is of type Context
, and Android’s Activity
class is a subclass of the abstract Context
class.
Compare Listings 1-2 and 1-3. In Listing 1-2, the intent learns the OtherActivity
's package name from a call to intent.setClassName
. But in Listing 1-3, the Intent
constructor gets the OtherActivity
's package name from this
— the SomeActivity
object’s context.
Each activity is part of an application, and an Application
instance is also a context. So in many programs, you can use any of the following method calls (instead of this
) to obtain a Context
instance:
getContext()
getApplicationContext()
getBaseContext()
In Listing 1-4, an activity from one app uses a context to call another app’s activity.
Listing 1-4: Calling Another App’s Activity Using an Explicit Intent with a Context
package my.pack;
import android.app.Activity;
import android.os.Bundle;
public class SomeActivity extends Activity {
// … code of some sort belongs here
try {
otherContext =
createPackageContext("other.pack",
Context.CONTEXT_IGNORE_SECURITY |
Context.CONTEXT_INCLUDE_CODE);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
Class<?> otherClass = null;
try {
otherClass = otherContext.getClassLoader().
loadClass("other.pack.OtherAppActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Intent intent = new Intent(otherContext, otherClass);
startActivity(intent);
}
Listing 1-4 is more complicated than Listing 1-3. But most of the complexity comes from the way Java loads classes. One way or another, Listing 1-4 creates an intent from a context and a class name, and then starts the intent’s activity.
“And one man in his time plays many parts, His acts being seven ages.”
— from As You Like It, by William Shakespeare
The human lifecycle is infancy, childhood, adolescence, young adulthood, middle age, old age, and finally, the end.
Android activities have a lifecycle, too. Here are the stages:
Paused: The Paused state is a kind of limbo between Active and Stopped. Officially, an activity is paused if it’s on the currently active stack but it’s partially obscured by another activity (such as a transparent activity or a non-full-screen activity that’s at the top of the stack).
In practice, an activity that’s transitioning from Active to Stopped goes through a brief period of being Paused, even if the user doesn’t see a “partially obscured” phase.
Most cultures have rites of passage. A rite of passage is something that you do when you transition from one life stage to another. For example, where I come from, a child does the following when transitioning to adolescence: “Ye shall stand at the highest point in all of thy land (which is normally the Dauphin Street station of the Frankford El train) and swing a raw fish thrice over thy head. All the while, thou shalt exclaim, ‘I shall be a troublesome, raving lunatic for the next few years.'”
Android activities have their own rites of passage, dubbed lifecycle methods. Figure 1-5 illustrates the methods.
Unlike people, activities don’t step predictably from one stage to the next. For example, a typical activity goes back and forth from being Active to Stopped and back to Active again, with several interludes of being Paused. And when Destroyed, an activity can unceremoniously be revived. For an activity, destruction and reconstruction are parts of the normal course of events.
An Android activity has seven lifecycle methods — namely, onCreate
, onRestart
, onStart
, onResume
, onPause
, onStop
, and onDestroy
. In addition, an activity has a few on
Something
methods (such as onSaveInstanceState
) that aren’t formally part of the lifecycle and aren’t guaranteed to be called. Anyway, Listing 1-5 contains a bunch of these methods.
Listing 1-5: Add Some Logging to Your Activity
package com.allmycode.demo1;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.View;
public abstract class MyActivity extends Activity {
abstract void logStuff(String message);
public void addBreak(View view) {
logStuff("------");
}
/** Called when the activity is created (either
* for the first time or after having been
* Destroyed. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
logStuff("onCreate");
}
/** Called when the activity transitions to
* Paused (on its way to Active) after having
* been Stopped.
*/
@Override
public void onRestart() {
super.onRestart();
logStuff("onRestart");
}
/** Called when the activity transitions to
* Paused (on its way to Active), either for
* the first time or after the activity has
* been Stopped.
*/
@Override
public void onStart() {
super.onStart();
logStuff("onStart");
}
/** Called when the activity transitions
* from Paused to Active.
*/
@Override
public void onResume() {
super.onResume();
logStuff("onResume");
}
/** Never called unless you set
* android:configChanges in the
* AndroidManifest.xml file.
*/
@Override
public void onConfigurationChanged
(Configuration config) {
super.onConfigurationChanged(config);
logStuff("onConfigurationChanged");
}
/** Usually (but not always) called during the
* transition from Active to Paused, or during
* the transition from Paused to Stopped.
*/
@Override
public void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
logStuff("onSaveInstanceState");
}
/** Called when the activity transitions from
* Active to Paused.
*/
@Override
public void onPause() {
super.onPause();
logStuff("onPause");
}
/** Called when the activity transitions from
* Paused to Stopped.
*/
@Override
public void onStop() {
super.onStop();
logStuff("onStop");
}
/** Called when the activity transitions from
* Stopped to Destroyed.
*/
@Override
public void onDestroy() {
super.onDestroy();
logStuff("onDestroy");
}
}
My goal in creating Listing 1-5 is to provide logging that helps you see the lifecycle methods in action. You can drop Listing 1-5 into almost any app to get a Burd’s-eye view of your activities and their transitions. To this end, I’ve created an app that lets you bounce back and forth among different kinds of activities. Listings 1-6, 1-7, and 1-8 describe the app’s main activity, and Figure 1-6 shows the main activity’s screen.
Listing 1-6: The com.allmycode.demo1.Demo1Activity Class
package com.allmycode.demo1;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class Demo1Activity extends MyActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
void logStuff(String message) {
Log.i("Demo1Activity", message);
}
public void startOtherActivity(View view) {
Intent intent = new Intent();
intent.setClassName("com.allmycode.demo1",
"com.allmycode.demo1.OtherActivity");
startActivity(intent);
}
public void startOtherAppActivity(View view) {
Intent intent = new Intent();
intent.setClassName("com.allmycode.demo1A",
"com.allmycode.demo1A.OtherAppActivity");
startActivity(intent);
}
public void startTransparentActivity(View view) {
Intent intent = new Intent();
intent.setClassName("com.allmycode.demo1",
"com.allmycode.demo1.TranslucentActivity");
startActivity(intent);
}
}
Listing 1-7: The Activity’s Layout File
<?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">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<Button android:layout_width="wrap_content"
android:id="@+id/button1"
android:onClick="startOtherActivity"
android:layout_height="wrap_content"
android:text="@string/start_this_app_other">
</Button>
<EditText android:layout_height="wrap_content"
android:id="@+id/editText"
android:layout_width="match_parent"
android:hint="Type anything here">
<requestFocus></requestFocus>
</EditText>
<include android:id="@+id/include1"
android:layout_width="wrap_content"
layout="@layout/add_break"
android:layout_height="wrap_content">
</include>
<Button android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="startOtherAppActivity"
android:text="@string/start_other_app">
</Button>
<Button android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="startTransparentActivity"
android:text="@string/start_translucent">
</Button>
</LinearLayout>
Listing 1-8: The add_break.xml File
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<Button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="addBreak"
android:text="@string/add_break">
</Button>
</LinearLayout>
I cover most of the Android coding tricks from Listings 1-6, 1-7, and 1-8 in Book I, Chapters 4 through 6. But these listings form the basis for an app that lets you experiment with the activity lifecycle. The next section describes what the widgets in Figure 1-6 (and a few other buttons) do.
No two lives are the same, so it would make sense that there is an infinite variety as well to the lifecycles of individual activities. If you dutifully followed my advice about downloading the code from Listings 1-6, 1-7, 1-8, and 1-9 from this book’s website, you can follow along as I demonstrate the kinds of curveballs I can throw at an activity lifecycle.
In Figure 1-6, you can click the Start This App’s Other Activity button to cover up the main activity with another activity from the same application. When you click the button, Android Studio’s Logcat panel displays the following entries:
1416-1416/com.allmycode.demo1 I/Demo1Activity: onPause
1416-1416/com.allmycode.demo1 W/OtherActivity: onCreate
1416-1416/com.allmycode.demo1 W/OtherActivity: onStart
1416-1416/com.allmycode.demo1 W/OtherActivity: onResume
1416-1416/com.allmycode.demo1 I/Demo1Activity:
onSaveInstanceState
1416-1416/com.allmycode.demo1 I/Demo1Activity: onStop
An OtherActivity
instance goes from not existing to being Active, and the Demo1Activity
instance goes from being Active to being Stopped.
In Figure 1-6, you can click the Add Break in Log File button to add an entry whose message is a dashed line. Press this button to help keep track of the parts in a long log file.
Your device’s Back button pops an activity off the task stack.
Imagine that with Demo1Activity
and OtherActivity
on the stack, you press the Back button. As a result, Android Studio’s Logcat panel displays the following entries:
1416-1416/com.allmycode.demo1 W/OtherActivity: onPause
1416-1416/com.allmycode.demo1 I/Demo1Activity: onRestart
1416-1416/com.allmycode.demo1 I/Demo1Activity: onStart
1416-1416/com.allmycode.demo1 I/Demo1Activity: onResume
1416-1416/com.allmycode.demo1 W/OtherActivity: onStop
1416-1416/com.allmycode.demo1 W/OtherActivity: onDestroy
Notice that pressing the Back button destroys the OtherActivity
instance.
In Figure 1-6, the Type Anything Here text field helps you understand when an activity’s state is preserved (and when it’s not).
Try this experiment:
Type something in the text field and then click the Start This App’s Other Activity button.
OtherActivity
obscures Demo1Activity
, and Demo1Activity
is Stopped.
Dismiss OtherActivity
with the Back button.
The Demo1Activity
reappears with your typed characters still in the text field. In spite of Demo1Activity
's being stopped, Android has preserved the state of Demo1Activity
.
Try another experiment:
Type something in the text field and then click the Home button.
The Home screen appears, and Demo1Activity
is Stopped.
Find the Demo1 icon and touch the icon to invoke Demo1Activity
.
The Demo1Activity
reappears with your typed characters still in the text field. Android has preserved the state of the Demo1Activity
.
Pressing Home is like telling Android, “I want to do something else while the current activity goes into ‘suspended animation.'”
Here’s another experiment:
Type something in the text field and then click the Back button.
The Demo1Activity
's screen goes away. In the Logcat panel, you see Demo1Activity
execute its onPause
, onStop
, and onDestroy
methods.
Find the Demo1 icon and touch the icon to invoke Demo1Activity
.
The Demo1Activity
reappears, but the Type Anything Here text field has been reinitialized. Android hasn’t preserved the Demo1Activity
's state.
Pressing Back is like saying to Android, “I’m done with this activity. Get me out ‘a' here!”
In the next experiment, you create a workaround for the behavior of Android’s Back button.
Demo1Activity
as follows:
package com.allmycode.demo1;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
public class Demo1Activity extends MyActivity {
EditText editText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
editText = (EditText) findViewById(R.id.editText);
}
@Override
public void onPause() {
super.onPause();
SharedPreferences prefs =
getPreferences(MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("EditTextString",
editText.getText().toString());
editor.commit();
}
@Override
public void onResume() {
super.onResume();
SharedPreferences prefs =
getPreferences(MODE_PRIVATE);
String str = prefs.getString("EditTextString", "");
editText.setText(str);
}
// You don’t have to add any code below this point
@Override
void logStuff(String message) {
// … Et cetera
Choose Run ⇒ Run ‘app'.
The app starts running with your modified Demo1Activity
code.
Type something in the text field and then click the Back button.
The Demo1Activity
's screen goes away. In the Logcat panel, you see Demo1Activity
execute its onPause
, onStop
, and onDestroy
methods.
Find the Demo1 icon and touch the icon to invoke Demo1Activity
.
The Demo1Activity
reappears with your typed characters still in the text field. Android didn’t automatically preserve the Demo1Activity
's state, but you used SharedPreferences
to save the state and then to restore the state.
Here’s your next experiment:
If you followed the previous set of instructions and added SharedPreferences
code, remove that code.
You can remove the entire onPause
method. Remove the onResume
method, too.
Turn your device sideways.
If you’re running an emulator, you can do a virtual turn by pressing Ctrl+F11. Your activity’s screen adjusts (from portrait to landscape or vice versa), and your typed characters are still in the text field.
But when you look at Android Studio’s Logcat panel, you see the following entries:
1437-1437/com.allmycode.demo1 I/Demo1Activity: onPause
1437-1437/com.allmycode.demo1 I/Demo1Activity:
onSaveInstanceState
1437-1437/com.allmycode.demo1 I/Demo1Activity: onStop
1437-1437/com.allmycode.demo1 I/Demo1Activity:
onDestroy
1437-1437/com.allmycode.demo1 I/Demo1Activity: onCreate
1437-1437/com.allmycode.demo1 I/Demo1Activity: onStart
1437-1437/com.allmycode.demo1 I/Demo1Activity: onResume
Surprise! In order to rotate your activity’s screen, Android destroys and then re-creates the activity. And between destruction and subsequent creation, Android preserves your activity instance’s state. The text field’s content is restored.
This leads you to another experiment:
Demo1Activity
's <activity>
element in the AndroidManifest.xml
file:
android:configChanges="orientation|screenSize"
Run the app and change the orientation of the emulator or the device.
You see the following entry in Android Studio’s Logcat panel:
8556-8556/com.allmycode.demo1 I/Demo1Activity﹕
onConfigurationChanged
The android:configChanges="orientation|screenSize"
attribute tells Android to notify the activity about changes in the device’s orientation or the size of the screen. So, if you tilt the device sideways, Android doesn’t do the usual routine of destroying and re-creating the activity. Instead, Android calls the activity’s onConfigurationChanged
method. In Listing 1-5, a call to the onConfigurationChanged
method creates a log entry to record the event.
Using the android:configChanges
attribute, you intercept Android’s default actions when the user tilts the device. You can then handle the orientation change with your own onConfigurationChanged
method. But the official documentation recommends against doing this. And, in giving this advice, the Android docs are probably correct. Here’s one reason why:
With very little tinkering, you can specify two different layouts for your activity — a portrait mode layout and a landscape mode layout. (For details, see Book IV, Chapter 1.) When the user tilts the device, Android can automatically switch from one layout to the other. This ensures that your app looks good no matter how the user holds the device. The trouble is, if you override the default behavior using the android:configChanges
attribute, the system doesn’t automatically switch layouts. You have to remember to switch layouts in your activity’s onConfigurationChanged
method. At best, this means more coding work for you. At worst, you forget to modify the onConfigurationChanged
method, and your app looks crummy when the user tilts the device.
Here’s one more experiment:
If you followed the previous set of instructions and added an android:configChanges
attribute in the AndroidManifest.xml
file, remove that attribute.
Actually, any newly created Android project will do just fine.
MainActivity
code.
@Override
public void onDestroy() {
super.onDestroy();
Log.i("MainActivity", Boolean.toString(isFinishing()));
}
On the emulator or the physical device, tap the Back button.
When you do, you see I/MainActivity: true
in the Logcat panel. Android calls your activity’s onDestroy
method, and a call to the onFinishing
method returns true
.
Tilt the emulator sideways (by pressing Ctrl+F11) or tilt the physical device sideways.
When you do, you see I/MainActivity: false
in the Logcat panel. Android calls your activity’s onDestroy method, but a call to the onFinishing
method returns false
.
By calling the onFinishing
method, you can distinguish a device-tilt onDestroy
call from a Back button onDestroy
call. That’s nice.
In Figure 1-6, you can click the Start Other App’s Activity button to cover up the main activity — you essentially cover the main activity with an activity from a different application. When you click the button, Android Studio’s Logcat panel displays the following entries:
4150-4150/com.allmycode.demo1 I/Demo1Activity: onPause
4150-4150/com.allmycode.demo1 I/Demo1Activity:
onSaveInstanceState
4150-4150/com.allmycode.demo1 I/Demo1Activity: onStop
4471-4497/com.allmycode.demo1A V/OtherAppActivity: onCreate
4471-4497/com.allmycode.demo1A V/OtherAppActivity: onStart
4471-4497/com.allmycode.demo1A V/OtherAppActivity: onResume
One difference between these entries and the entries in previous examples is that these entries use two PID numbers. In this example, the Demo1Activity
has PID 4150
, and the OtherAppActivity
has PID 4471
. As promised, two different apps run in two different operating system processes, and each process has its own PID. You can get independent verification of this fact by examining the Devices panel in the Android Device Monitor.
To see the Devices panel, do the following:
In the Android Device Monitor’s menu, choose Window ⇒ Open Perspective ⇒ DDMS (default).
With the DDMS perspective open, the Devices panel is on the right side of the Android Device Monitor.
Figure 1-7 shows you the Devices panel.
In Figure 1-7, the process running Demo1Activity
(in package com.allmycode.demo1
) has PID 4150
. And the process running OtherAppActivity
(in package com.allmycode.demo1A
) has PID 4471
.
As the sun sets on the “Starting another app’s activity” section, you’re running two com.allmycode
processes. (Refer to Figure 1-7.) On your emulator’s screen, the com.allmycode.demo1.Demo1Activity
is obscured by the com.allmycode.demo1A.OtherAppActivity
.
So you can conduct another experiment:
Get your emulator (or device) in the state described at the end of the “Starting another app’s activity” section.
To do so, start the app whose main activity is in Listing 1-6. Then click the Start Other App’s Activity button.
Press the emulator’s Home button.
See Figure 1-8. You’re not pressing the Back button, so you’re not backing out of the OtherAppActivity
or the Demo1Activity
. Those two activities are Stopped, not Destroyed. (You can verify this by looking at the Android Studio’s Logcat panel.)
In Android Studio, open the Hog app that I’ve included in the download for this book. Run that Hog app in the same emulator or device that you used in Step 1.
My Hog app’s sole purpose is to inundate your emulator with processes. The Hog app displays a button with a number on it. (See Figure 1-9.) The button’s number tells you how many processes the Hog has launched. When you click the button, the app starts a new process running on your emulator and increments the number on the button.
Keep an eye on Android Studio’s Devices panel as you repeatedly click the Hog app’s button.
You can see a list of Hog processes in the Devices panel. (See Figure 1-10.)
To flood the emulator with processes, my Hog app adds an android:process
attribute to each of the app’s activities. I also use some really messed-up, repetitive code.
At some point, you see the com.allmycode.demo1
process disappear from the Devices pane. (For me, this happens when I start the 16th process, but your mileage may vary. See Figure 1-11.) Android has reclaimed memory by killing off a process. After all, the com.allmycode.demo1.Demo1Activity
instance is Stopped, so Android figures it can destroy that activity. The com.allmycode.demo1A.OtherAppActivity
is also Stopped. But the last time you saw OtherAppActivity
, that activity was at the top of a task stack.
Android kills a process in order to reclaim space, and the user has no clue that the process has been killed. This is business as usual for the Android operating system.
At this point in the experiment, the Demo1Activity
is Stopped, the activity’s process has been killed, and the activity isn’t on top of its current task.
Press Home and then the Apps button to return to the emulator’s Apps screen, and click the Demo1 icon (the icon for the com.allmycode.demo1.Demo1Activity
instance).
The OtherAppActivity
reappears on the screen. (See Figure 1-12.) Android interprets your click as a wish to return to the top of the task stack containing Demo1Activity
, and not to Demo1Activity
itself. In the Logcat panel, OtherAppActivity
has been created, started, and resumed.
1796-1796/com.allmycode.demo1A V/OtherAppActivity:
onCreate
1796-1796/com.allmycode.demo1A V/OtherAppActivity:
onStart
1796-1796/com.allmycode.demo1A V/OtherAppActivity:
onResume
In Android Studio’s Devices pane, you still don’t see com.allmycode.demo1
.
The Logcat panel might be filtering for the com.allmycode.hog
app, in which case, you don’t see com.allmycode.demo1
or com.allmycode.demo1A
entries. If this is the case, select No Filters in the drop-down box in the upper-right corner of the Logcat panel. Then, to filter for demo1
and demo1A
entries, type demo
in the search field immediately above the Logcat panel.
Press the emulator’s Back button.
Android remembers that com.allmycode.demo1.Demo1Activity
was hidden (conceptually) underneath OtherAppActivity
on the task stack. Because the com.allmycode.demo1
process no longer exists, Android creates a new process with a new PID to run the com.allmycode.demo1.Demo1Activity
code.
As a result, Demo1Activity
appears on your emulator’s screen, com.allmycode.demo1
reappears in Android Studio’s Devices panel with a new PID, and the following entries appear in Android Studio’s Logcat panel:
1796-1796/com.allmycode.demo1A V/OtherAppActivity:
onPause
1819-1819/: V/Demo1App: onCreate
1819-1819/: I/Demo1Activity: onCreate
1819-1819/: I/Demo1Activity: onStart
1819-1819/: I/Demo1Activity: onResume
1796-1796/com.allmycode.demo1A V/OtherAppActivity:
onStop
1796-1796/com.allmycode.demo1A V/OtherAppActivity:
onDestroy
Unbeknownst to the user, Android has restored the Demo1Activity
in a new process.
In Figure 1-6, clicking the Start Translucent Activity button does what you think it should do. The button makes Android invoke a see-through activity. (App development terminology tends to blur the difference between “translucent” and “transparent.” Get it? “Blur” the difference?) Of course, you can’t invoke a translucent activity unless you have a translucent activity to invoke. So Listing 1-9 shows you how to create a translucent activity.
Listing 1-9: The AndroidManifest.xml File for One of This Chapter’s Big Projects
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android=
"http://schemas.android.com/apk/res/android"
package="com.allmycode.demo1"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<application android:icon="@drawable/icon"
android:label="@string/app_name"
android:name=".Demo1App">
<activity android:name=".Demo1Activity"
android:label="@string/app_name" >
<intent-filter>
<action android:name=
"android.intent.action.MAIN" />
<category android:name=
"android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".OtherActivity" />
<activity android:name=".TranslucentActivity"
android:theme=
"@android:style/Theme.Translucent" />
</application>
</manifest>
To create a translucent activity, create a Java class that extends Android’s Activity
class. Then, in your AndroidManifest.xml
file, declare the activity’s theme
to be Android’s predefined Theme.Translucent
style.
My TranslucentActivity
class has only one button — an Add Break in Log File button in the center of the activity. So, after pressing the Start Translucent Activity button in Figure 1-6, I see the stuff in Figure 1-13. True to its word, Android superimposes the Translucent Activity’s button on top of the next activity on the stack.
Referring to the list of activity states described at the beginning of this section, one of the Paused state’s duties is to house activities that are partially obscured. So, if you look at Android Studio’s Logcat panel after clicking the Start Translucent Activity button, you see the following entries:
1419-1419/com.allmycode.demo1 I/Demo1Activity: onPause
1419-1419/com.allmycode.demo1 W/TranslucentActivity: onCreate
1419-1419/com.allmycode.demo1 W/TranslucentActivity: onStart
1419-1419/com.allmycode.demo1 W/TranslucentActivity: onResume
1419-1419/com.allmycode.demo1 I/Demo1Activity:
onSaveInstanceState
Demo1Activity
pauses but doesn’t stop.
In Book I, Chapter 6, I compare an intent to a method call. To start an activity, you don’t call a method. Instead, you fire up an intent.
So far, so good. But what feature of an intent takes the place of a method call’s return value? Listings 1-10 and 1-11 have the answer.
Listing 1-10: An Activity Asks for a Result
package com.allmycode.results;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class GetResultActivity extends Activity {
final int MY_REQUEST_CODE = 42;
TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textView = (TextView) findViewById(R.id.textView);
}
public void onButtonClick(View v) {
Intent intent = new Intent();
intent.setClassName("com.allmycode.results",
"com.allmycode.results.GiveResultActivity");
startActivityForResult(intent, MY_REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode,
int resultCode,
Intent intent) {
if (requestCode == MY_REQUEST_CODE &&
resultCode == RESULT_OK) {
textView.setText(intent.getStringExtra("text"));
}
}
}
Listing 1-11: An Activity Provides a Result
package com.allmycode.results;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
public class GiveResultActivity extends Activity {
EditText editText;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.giver);
editText = (EditText) findViewById(R.id.editText);
}
public void onButtonClick(View arg0) {
Intent intent = new Intent();
intent.putExtra
("text", editText.getText().toString());
setResult(RESULT_OK, intent);
finish();
}
}
The actions of Listings 1-10 and 1-11 take place in three stages. First, the user sees the GetResultActivity
in Listing 1-10. (See Figure 1-14.)
When the user clicks the Get A Result button, Android calls startActivityForResult(intent, MY_REQUEST_CODE)
.
The startActivityForResult
method takes an intent and a request code. In Listing 1-10, the intent points explicitly to the activity being started. The request code is any int
value. The request code identifies the return result when the result arrives. (You can call startActivityForResult
more than once before you get any results. When results arrive, you use the request code to distinguish one result from another.)
After clicking the button in Figure 1-14, the user sees the GiveResultActivity
in Listing 1-11. (See Figure 1-15.)
The user types text into the text field in Figure 1-15 and then clicks the Go Back button. The button click causes the code in Listing 1-11 to create an intent. The intent has extra information — namely, the user’s text input.
The call to setResult
sends a result code (RESULT_OK
, RESULT_CANCELED
, or any positive int
value that’s meaningful to the receiver) along with the intent full of useful information.
At the end of Listing 1-11, the finish
method call ends the run of the activity shown in Figure 1-15. The screen returns to the GetResultActivity
. (See Figure 1-16.)
At this point, Android calls the onActivityResult
method in Listing 1-10. The method uses the result in some way or other. (In this example, the onActivityResult
method simply displays the result in a TextView
element.)
In any operating system, things come and things go. Users log on and log off, and activities are created and destroyed. But what about applications? Applications are created and terminated.
If you check Listing 1-9, you see an application
element with an android:name=".Demo1App”
attribute. An app doesn’t need an android:name
attribute, but if it has one, you must create a class with the name that you specify. You can keep track of an app’s global values (values that transcend the app’s individual activities) with this class. Listing 1-12 sheds light on the situation.
Listing 1-12: An Android Application
package com.allmycode.demo1;
import android.app.Application;
import android.content.res.Configuration;
import android.util.Log;
public class Demo1App extends Application {
private static final String CLASSNAME = "Demo1App";
@Override
public void onCreate() {
Log.v(CLASSNAME, "onCreate");
}
@Override
public void onConfigurationChanged
(Configuration config) {
Log.v(CLASSNAME, "onConfigurationChanged");
}
@Override
public void onLowMemory() {
Log.v(CLASSNAME, "onLowMemory");
}
@Override
public void onTerminate() {
Log.v(CLASSNAME, "onTerminate");
}
}
Android’s Application
class has only four of its own methods — the methods declared in Listing 1-12. In previous sections, I didn’t draw attention to the log entries from Listing 1-12. But in this section, you can watch for the name “Demo1App” as you follow these instructions:
Open this chapter’s example project (the project from this chapter’s “Lifecycle methods” section).
Even if you stopped working with this project a while ago, the project might still be consuming part of your emulator’s memory. In this section, you want to start afresh. So, if the project is still in memory, you want to remove it. Here’s how:
In the Devices panel, look for com.allmycode.demo1
and com.allmycode.demo1A
.
If you don’t see com.allmycode.demo1
or com.allmycode.demo1A
in the Devices panel, that’s okay. In fact, the reason you’re looking for them is to get rid of them if they’re there.
The Devices panel is part of the Android Device Monitor. For help finding the Devices panel, refer to the section “Starting another app’s activity,” earlier in this chapter.
com.allmycode.demo1
in the Devices panel, select that entry. Look to the left of the Devices panel for an icon displaying a Stop sign. (See Figure 1-17.) Click that icon to terminate the com.allmycode.demo1
process.Repeat Step 3, this time looking for a com.allmycode.demo1A
entry.
Be ruthless if you must.
In Android Studio’s main menu, choose Run ⇒ Run ‘app' to run the project anew.
When the app starts running, you see the following entry in the Logcat panel:
1701-1701/com.allmycode.demo1 V/Demo1App﹕ onCreate
The entry appears because Android calls the onCreate
method in Listing 1-12.
Android calls the onCreate
method in Listing 1-12 only if your AndroidManfest.xml
file’s application tag has an android:name
attribute.
<application
…
android:name=".Demo1App"
… Etc.
If you have trouble finding a Demo1App
entry in the Logcat panel, you can quickly filter the entries. To do so, type Demo1App in the text field immediately above the Logcat panel.
Change your emulator’s orientation (from portrait to landscape, for example).
As a result, you see the following entry in the Logcat panel:
1701-1701/com.allmycode.demo1 V/Demo1App﹕
onConfigurationChanged
Android has notified your app about the device’s configuration change.
Press the emulator’s Home button. Then start a new app (the Phone app, for example).
Your app’s activity is Stopped, so you don’t see evidence of the app’s run on your emulator’s screen. But if you check Android Studio’s Devices panel, you still see a com.allmycode.demo1
entry. The application is still alive.
Change your emulator’s orientation (from landscape to portrait, for example).
Again, you see the following onConfigurationChanged
entry in the Logcat panel:
1701-1701/com.allmycode.demo1 V/Demo1App﹕
onConfigurationChanged
Android has notified all running apps about the device’s configuration change.
Restart or return to the Hog app (the app from this chapter’s section “Overloading the system” ).
If the Hog app is still running in your emulator, use the emulator’s Recents button to return to the Hog app. Otherwise, choose Run ⇒ Run ‘app' in Android Studio’s main menu.
Click the Hog app’s button until com.allmycode.demo1
disappears from Android Studio’s Devices panel.
If you don’t do too much filtering in the Logcat panel, you see messages from Android about the termination of com.allmycode.demo1
.
553-947/system_process I/ActivityManager:
Killing 1365:com.allmycode.demo1/u0a70 (adj 14):
cached #17
553-605/system_process W/InputDispatcher:
channel '2c2e3292
com.allmycode.demo1/com.allmycode.demo1.Demo1Activity
(server)' ~ Consumer closed input channel or an error
occurred. events=0x9
553-605/system_process E/InputDispatcher:
channel '2c2e3292
com.allmycode.demo1/com.allmycode.demo1.Demo1Activity
(server)' ~ Channel is unrecoverably broken and will
be disposed!
553-941/system_process I/WindowState: WIN DEATH:
Window{2c2e3292 u0
com.allmycode.demo1/com.allmycode.demo1.Demo1Activity}
But here’s a surprise. You see none of the log messages from Listing 1-12 (not even when Android kills your apps' processes). If you read the fine print in Android’s docs, you see the following:
While the exact point at which this [onLowMemory
method] will be called is not defined, generally it will happen around the time all background process have [sic] been killed… . [TheonTerminate
method] will never be called on a production Android device, where processes are removed by simply killing them; no user code (including this callback) is executed when doing so.
During my run of this chapter’s apps, Android never calls either onLowMemory
or onTerminate
.
Some of Android’s doc entries are concisely worded. This makes the docs easy to misinterpret. Your confidence in a doc entry’s interpretation should be proportional to your experience using and testing that entry’s claims.
Return to your original app’s task (as in Step 5 of the earlier section “Overloading the system” ).
You see the OtherApp
startup entries:
1899-1899/com.allmycode.demo1 V/Demo1App: onCreate
1899-1899/com.allmycode.demo1 I/Demo1Activity: onCreate
1899-1899/com.allmycode.demo1 I/Demo1Activity: onStart
1899-1899/com.allmycode.demo1 I/Demo1Activity: onResume
One of your “other” app’s activities is on top of the stack. So Android creates a new process (with a new PID) for your other app.
34.231.180.210