Android API Basics

In the rest of the chapter we'll concentrate on playing around with those Android APIs that are relevant to our game development needs. For this, we'll do something rather convenient: we'll set up a test project that will contain all our little test examples for the different APIs we are going to use. Let's get started.

Creating a Test Project

From the last section we already know how to set up all our projects. So the first thing we do is execute the ten steps outlined earlier. I followed these steps, creating a project named ch04-android-basics with a single main activity called AndroidBasicsStarter. We are going to use some older and some newer APIs, so I set the minimum SDK version to 3 (Android 1.5) and the build target as well as the target SDK version to 9 (Android 2.3). From here on, all we'll do is create new activity implementations, each demonstrating parts of the Android APIs.

But remember that we only have one main activity. So what does our main activity look like? We want a convenient way to add new activities as well as the ability to easily start a specific activity. With one main activity, it should be clear that that activity will somehow provide us with a means to start a specific test activity. The main activity will be specified as the main entry point in the manifest file, as discussed earlier. Each additional activity we add will be specified without the <intent-filter> child element. We'll start those programmatically from the main activity.

The AndroidBasicsStarter Activity

The Android API provides us with a special class called ListActivity, which derives from the Activity class we used in the Hello World project. The ListActivity is a special type of activity who's single purpose it is to display a list of things (e.g., strings). We use it to display the names of our test activities. When we touch one of the list items, we'll start the corresponding activity programmatically. Listing 4-1 shows the code for our AndroidBasicsStarter main activity.

Listing 4-1. AndroidBasicsStarter.java, Our Main Activity Responsible for Listing and Starting All Our Tests package com.badlogic.androidgames;

import android.app.ListActivity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.ArrayAdapter; import android.widget.ListView;

public class AndroidBasicsStarter extends ListActivity {

String tests[] = { "LifeCycleTest", "SingleTouchTest", "MultiTouchTest", "KeyTest", "AccelerometerTest", "AssetsTest", "ExternalStorageTest", "SoundPoolTest", "MediaPlayerTest",

"FullScreenTest", "RenderViewTest", "ShapeTest", "BitmapTest", "FontTest", "SurfaceViewTest" };

"FullScreenTest", "RenderViewTest", "ShapeTest", "BitmapTest", "FontTest", "SurfaceViewTest" };

public void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, tests));

@Override protected void onListItemClick(ListView list, View view, int position, long id) {

super.onListItemClick(list, view, position, id); String testName = tests[position]; try {

Class clazz = Class

.foiName("com.badlogic.androidgames." + testName); Intent intent = new Intent(this, clazz); startActivity(intent); } catch (ClassNotFoundException e) { e.printStackTrace();

The package name I chose is com.badlogic.androidgames. The imports should also be pretty self-explanatory; those are simply all the classes we are going to use in our code. Our AndroidBasicsStarter class derives from the ListActivity class—still nothing special. The field tests is a string array holding the names of all the test activities our starter application should display. Note that the names in that array are the exact Java class names of the activity classes we are going to implement later on.

The next piece of code should be familiar; it's the onCreate() method we have to implement for each of our activities, which will be called when the activity is created. Remember that we must call the onCreate() method of the base class of our activity. It's the first thing we must do in the onCreate() method of our own Activity implementation. If we don't, an exception will be thrown and the activity will not be displayed.

With that out of the way, the next thing we do is call a method called setListAdapter(). That method is provided to us by the ListActivity class we derived from. It lets us specify the list items we want the ListActivity to display for us. These need to be passed to the method in the form of a class instance that implements the ListAdapter interface. We use the convenient ArrayAdapter to do this. The constructor of this class takes three arguments: the first is our activity, the second one I'll explain in a bit, and the third is the array of items the ListActivity should display. We happily specify the tests array we defined earlier for the third argument, and that's all we need to do.

So what's this second argument to the ArrayAdapter constructor? To explain this, I'd have to go through all the Android UI API stuff, which we are not going to use in this book. So instead of wasting pages on something we are not going to need, I'll give you the quick-and-dirty explanation: each item in the list is displayed via a View. The argument defines the layout of each View, along with what type each View has. The value android.R.layout.simple_list_item_l is a predefined constant provided by the UI API for getting up and running quickly. It stands for a standard list item View that will display text. Just as a quick refresher, a View is a UI widget on Android, such as a button, a text field, or a slider. We talked about that while dissecting the HelloWorld activity in Chapter 2.

If we start our activity with just this onCreate() method, we'll see something like in Figure 4-2.

* fe MA^f M tg 5:35pm Android Basics

LifeCycleTest SingleTouchTest MultiTouchTest KeyTest

AccelerometerTest

AssetsTest

ExternalStorageTest

^nnnHPnnlTp<;t

Figure 4-2. Our test starter activity, which looks fancy but doesn't do a lot yet

Now let's make something happen when a list item is touched. We want to start the respective activity that is represented by the list item we touched.

Starting Activities Programmatically

The ListActivity class has a protected method called onListItemClick() that will be called when an item is clicked. All we need to do is to override that method in our AndroidBasicsStarter class. And that's exactly what we did in Listing 4-1.

The arguments to this method are the ListView that the ListActivity uses to display the items, the View that got touched and that's contained in that ListView, the position of the touched item in the list, and an ID, which doesn't interest us all that much. All we really care about is the position argument.

The onListItemClicked() method starts of by being a good citizen and calls the base class method first. This is always a good thing to do if we override methods of an activity. Next we fetch the class name from the tests array based on the position argument. That's the first piece of the puzzle.

We discussed earlier that we can start activities we defined in the manifest file programmatically via an Intent. The Intent class has a nice and simple constructor to do this, which takes two arguments: a Context instance and a Class instance, which represents the Java class of the activity we want to start.

The Context is an interface that provides us with global information about our application. It is implemented by the Activity class, so we simply pass the this reference to the Intent constructor.

To get the Class instance representing the activity we want to start, we use a little reflection, which you will probably familiar with if you've worked with Java. The static method Class.forName() takes a string containing the fully qualified name of a class we want to get a Class instance for. All the test activities we'll implement later will be contained in the com.badlogic.androidgames package. Concatenating the package name with the class name we fetched from the tests array will give us the fully qualified name of the activity class we want to start. We pass that name to Class.forName() and get a nice Class instance that we can pass to the Intent constructor.

Once the Intent is constructed, we can start it with a call to the startActivity() method. That method is also defined in the Context interface. Since our activity implements that interface, we just call its implementation of that method. And that's it!

So how will our application behave? First the starter activity will be displayed. Each time we touch an item on the list, the corresponding activity will be started. The starter activity will be paused and go into the background. The new activity will be created by the intent we send out and replace the starter activity on the screen. When we press the back button on the phone, the activity is destroyed and the starter activity is resumed, taking back the screen.

Creating the Test Activities

When we create a new test activity, we have to perform the following steps:

1. Create the corresponding Java class in the com.badlogic.androidgames package and implement its logic.

2. Add an entry for it in the manifest file, using whatever attributes it needs (e.g., android:configChanges or android:screenOrientation). Note that we won't specify an <intent-filter> element, as we'll start the activity programmatically.

3. Add the activity's class name to the tests array of the AndroidBasicsStarter class.

As long as we stick to this procedure, everything else will be taken care of by the logic we implemented in the AndroidBasicsStarter class. The new activity will automatically show up in the list and can be started by a simple touch.

One thing you might wonder is whether the test activity that gets started on a touch is running in its own process and VM. It is not. An application composed of activities has something called an activity stack. Each time we start a new activity, it gets pushed onto that stack. When we close the new activity, the last activity that got pushed to the stack will get popped and resumed, becoming the new active activity on the screen.

This also has some other implications. First, all the activities of the application (those on the stack that are paused and the one that is active) share the same VM. They also share the same memory heap. That can be a blessing and a curse. If you have static fields in your activities, they will get memory on the heap as soon as they are started. Being static fields, they will survive the destruction of the activity and the subsequent garbage collection of the activity instance. This can lead to some nice memory leaks if you carelessly use static fields. Think twice before using a static field.

As stated a couple of times already, though, we'll only ever have a single activity in our actual games. The preceding activity starter is an exception to this rule to make our lives a little easier. But don't worry, we'll have enough opportunities to get into trouble even with a single activity.

NOTE: This is as deep as we'll get into Android UI programming. From here on we'll always use a single View in an activity to output things and receive input. If you want to learn about things like layouts, view groups, and all the bells and whistles the Android UI library offers, I suggest you check out Mark Murphy's book, Beginning Android 2 (Apress, 2010), or the excellent developer guide on the Android Developers site.

The Activity Life Cycle

The first thing we have to figure out when programming for Android is how an activity behaves. On Android, this is called the activity life cycle. It describes the states and transitions between those states that an activity can live through. Let's start by discussing the theory behind this.

In Theory

An activity can be in three states:

Running: In this state, it is the top-level activity that takes up the screen and directly interacts with the user.

Paused: This happens when the activity is still visible on the screen but partially obscured by either a transparent activity or a dialog, or if the phone screen is locked. A paused activity can be killed by the Android system at any point in time

(e.g., due to low memory). Note that the activity instance itself is still alive and kicking in the VM heap and waiting to be brought back to a running state.

Stopped: This happens when the activity is completely obscured by another activity and thus is no longer visible on the screen. Our AndroidBasicsStarter activity will be in this state if we start one of the test activities, for example. It also happens when a user presses the home button to go to the home screen temporarily. The system can again decide to kill the activity completely and remove it from memory if memory gets low.

In both the paused and stopped states, the Android system can decide to kill the activity at any point in time. It can do so politely, by first informing the activity of that by calling its finished() method, or by being bad and silently killing its process.

The activity can be brought back to a running state from a paused or stopped state. Note again that when an activity is resumed from a paused or stopped state, it is still the same Java instance in memory, so all the state and member variables are the same as before the activity was paused or stopped.

An activity has some protected methods that we can override to get informed of state changes:

Activity.onCreate(): This is called when our activity is started up for the first time. Here we set up all the UI components and hook into the input system. This will only get called once in the life cycle of our activity.

Activity.onRestart(): This is called when the activity is resumed from a stopped state. It is preceded by a call to onStop().

Activity.onStart(): This is called after onCreate() or when the activity is resumed from a stopped state. In the latter case, it is preceded by a call to onRestart().

Activity.onResume(): This is called after onStart() or when the activity is resumed from a paused state (e.g., the screen is unlocked).

Activity.onPause(): This is called when the activity enters the paused state. It might be the last notification we receive, as the Android system might decide to silently kill our application. We should thus save all state we want to persist in this method!

Activity.onStop(): This is called when the activity enters the stopped state. It is preceded by a call to onPause(). This means that before an activity is stopped, it is paused first. As with onPause(), it might be the last thing we get notified of before the Android system silently kills the activity. We could also save persistent state here. However, the system might decide not to call this method and just kill the activity. As onPause() will always be called before onStop() and before the activity is silently killed, we'd rather save all our stuff in the onPause() method.

Activity.onDestroy(): This is called at the end of the activity life cycle when the activity is irrevocably destroyed. It's the last time we can persist any information we'd like to recover the next time our activity is created anew. Note that this method might actually never be called if the activity was destroyed silently after a call to onPause() or onStop() by the system.

Figure 4-3 illustrates the activity life cycle and the method call order.

Figure 4-3 illustrates the activity life cycle and the method call order.

C Activity is shut down J

Figure 4-3. The mighty, contusing activity life cycle

Here are the three big lessons we should take away from this:

Before our activity enters the running state, the onResume() method is always called, no matter whether we resume from a stopped state or from a paused state. We can thus safely ignore the onRestart() and onStart() methods. We don't care whether we resumed from a stopped or a paused state. For our games, it is only necessary to know that we are now actually running, and the onResume() method signals that to us.

The activity can be destroyed silently after onPause(). We should thus never assume that either onStop() or onDestroy() get called. We also know that onPause() will always be called before onStop(). We can thus safely ignore the onStop() and onDestroy() methods, and just override onPause(). In this method, we have to make sure that all the states we want to persist, like high-scores and level progress, get written to an external storage like the SD card. After onPause(), all bets are off, and we won't know whether our activity will ever get the chance to run again.

We know that onDestroy() might never be called if the system decides to kill the activity after onPause() or onStop(). However, sometimes we'd like to know whether the activity is actually going to be killed. So how do we do that if onDestroy() is not going to get called? The Activity class has a method called Activity.isFinishing() that we can call at any time to check whether our activity is going to get killed. We are guaranteed that at least the onPause() method is called before the activity is killed. All we need to do is call this isFinishing() method inside the onPause() method to decide whether the activity is going to die after the onPause() call.

This makes life a lot easier. We only override the onCreate(), onResume(), and onPause() methods.

In onCreate(), we set up our window and UI component that we render to and receive input from.

In onResume(), we (re)start our main loop thread (discussed in the last chapter).

In onPause(), we simply pause our main loop thread, and if Activity.isFinishing() returns true, we also save any state we want to persist to disk.

Many people struggle with the activity life cycle, but if we follow these simple rules, our game will be capable of handling pausing and resuming as well as cleaning up.

0 0

Post a comment