I’m sure you’re ready to roll up your sleeves and write more code. However, there are a few topics in the realm of theory and design to cover in detail first. In this chapter, we’ll cover the basics of some essential building blocks, including the files, parts, and terms that make up a simple Android application; the Activity
class, which controls a single screen; the Intent
class, Android’s powerful communications class; and the Application
singleton class, which can be accessed from all your components. There is one more key component for your Android toolset, the Fragment
, but we’ll cover that later on; it’s not necessary to understanding the basics.
I recommend you open your IDE and follow along as I cover the working parts of an Android application. I’ll be using the Android project that you created in Chapter 1.
Any mobile application, in its most basic form, consists of a single screen that is launched by clicking an icon on the device’s main screen.
When the SDK creates a basic Android project, it also creates several files and important directories.
As with any project, before you start building the structure it’s important to at least take a quick look over the blueprints. In an Android project, it is the files and folders that make up that structure.
In Eclipse, you will find your source folders and Android manifest right in the root directory:
AndroidManifest.xml
/res
/src
In Android Studio, you will find your source folders and Android manifest under
/
projectName/src/main/AndroidManifest.xml
/
projectName/src/main/java
/
projectName/src/main/res
Your application can have only one AndroidManifest.xml
file, so I’ll refer to this file and concept simply as the manifest.
The AndroidManifest.xml
file is your portal to the rest of the phone. In it, you’ll describe which of your components should receive what events. You’ll also declare, in the manifest file, what hardware and software your app will need permission to access. First, let’s take a look at the <manifest>
declaration in the AndroidManifest.xml
file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.peachPit"
android:versionCode="1"
android:versionName="1.0">
There are a few noteworthy items in this code. The package
definition tells Android which Java package to look in for the class files that make up the components of your application. The next two variables are not particularly important right now, but they will become vital once you’re ready to ship your application to the Google Play Store. The versionCode
is the number that helps Google Play alert users that an update is available. The versionName
is a string that the application menus and Google Play display to the user as the current version of your app.
Open your manifest file, located in <
ProjectRoot>/AndroidManifest.xml
(If you don’t open it in the IDE, make sure you open it with a text editor.) In a typical Android application, activities are the backbone of the operation. Essentially, their purpose is to control what is displayed on the screen. They bridge the gap between the data you wish to display and the UI layout files and classes that do the work of displaying the data. If you’re familiar with the popular Model-View-Controller (MVC) architecture, the activity would be the control for a screen. Here’s what the activity declaration looks like in the manifest file:
<activity android:name=".MainActivity"
android:label="@string/app_name">
<!-- More on how the intent-filter works in the next section-->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
The android:name
tag tells the system what to place on the end of your package
(from the manifest declaration) to find your class definition. For example, in my sample project at com.peachpit.MainActivity
, the class loader looks for a class that extends the Activity
class.
In order to be found, the file must reside under the src/com/peachPit
directory. This is standard operating procedure for the language that Android uses.
The activity, if used correctly, is an object that specifically controls a single screen.
Let’s talk about this mythical activity in terms of a real-world RSS news feed reader—a case study that can quickly explain what pages of theory would often miss. A developer typically uses one activity to list all feeds to which a user has subscribed. When a user taps a feed, the developer uses a second activity to display a list of available articles for that particular news feed. Lastly, when a user clicks a particular story, the developer uses a third activity to display article details.
It’s easy to see how activities fill a certain role (subscription list, article list, article detail). At the same time, the activities are general, in that the article list should be able to display a list of articles from any RSS feed, and the article-details activity should show the details from any article found through an RSS reader.
In its simplest form, an activity is an object that extends the Activity
class. It only needs to implement the onCreate
method and call setContentView
to get a visible interface. Here’s what your activity looks like by default when you create a new project:
public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
In this code, the device calls the onCreate
method as the activity is starting. onCreate
tells the UI system that the setContentView
method specifies the main layout file for this activity. Each activity can have one and only one content view, so once you set it, it can’t be changed. This is how the Android SDK forces you to use a new activity for each screen, because each time you want to change your root content view, you’ll need a different activity.
In most cases, the best way to understand something is to use it. With that in mind, let’s add a new activity to the project you created in Chapter 1. This will explain how the activity works, its lifecycle, and what you need to know while working with it. Here are the general steps you’ll need to follow.
1. Add an entry for the new activity to your manifest.
2. Create a new class that extends the Activity
class.
3. Create a new file containing the XML layout instructions for this new activity, and add a new string literal for the layout to display (don’t worry, this sounds a lot harder than it actually is).
4. When all the files are in place, you’ll need to launch this new activity from your existing one.
We need to add the activity declaration to the manifest so that the Android system knows where to find your new activity when it comes time to load and launch it.
1. Open the AndroidManifest.xml
file in your IDE.
2. Add the following line inside the <application>
tag and directly after the </activity>
closing tag of the previous declaration:
<activity android:name=".NewActivity"/>
This little line tells the system where to find the new activity in your application package. In the case of my demo, the class loader knows to look for the activity at com.peachPit.NewActivity
.
Next, you’ll need to put a file there for it to find.
There are several ways to create a new activity, but here is the easiest way to do it in your IDE.
1. Right-click (or Control-click) the package name you’ve chosen (mine is com.peachPit
).
2. Select New, then select Class.
3. Name your new class NewActivity.
4. In the Superclass field, click the browse button.
5. In the Superclass window search field, start typing Activity until you see Activity–android.app populate. When you see it, select it, and click OK.
Although a name is technically all you need to create a new class, adding the superclass from this window will ensure that you are always extending the class you intended. This can really be a lifesaver when you start dealing with compatibility components that have the same name across different libraries.
6. Make the following highlighted changes to your code:
package com.peachPit;
import android.app.Activity;
import android.os.Bundle;
public class NewActivity extends Activity{
@Override public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
}
}
Notice that this code looks very similar to what is already in your existing activity. Let’s make it a little bit different. If the imports don’t automatically appear for you, choose Source > Organize Imports. This will make sure you have all the imports you need included, as well as remove any unused ones.
7. In the res/values/strings.xml
file, add the following highlighted lines in the <resources>
tag under the existing strings:
<resources>
<!—other strings are omitted here for brevity-->
<string name="new_activity_text">
Welcome to the New Activity!
</string>
</resources>
Note
"new_activity_text"
is just a variable name. You can name it whatever makes the most sense for you and your project.
In these lines, you told Android that you want a new string with the name new_activity_text
that can be accessed through Android’s resource manager. You’ll learn much more about the contents of the /values
folder in later chapters. Next, you need to create a layout file for your new activity.
Here’s how you create a new layout.
1. Create a file named activity_new.xml
inside the res/layout/
folder. It should sit next to the existing main.xml
file (which is used by your existing activity). This activity_new.xml
file should look almost exactly like main.xml
, except you’ll need to add a reference to the string you just created.
2. Inside activity_new.xml
, insert the following code to create a basic layout with a text view in it. By adding android:id="@+id/new_activity_text_view"
, you are assigning this TextView
an ID so your Java code can reference it (you’ll learn more about TextView
s later; for now you should know that they’re Android views that show text on the screen):
<?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" >
<TextView
android:id="@+id/new_activity_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/new_activity_text" />
</LinearLayout>
I devote Chapter 3 to resource management and user interface design, but for now just keep in mind that the @
prefix is how you tell Android that you want to reference an ID, string, or drawable that is defined elsewhere as a resource.
Now that you have a new layout with a shiny new string, you’ll need to tell the NewActivity
class that you want it to use this particular layout file.
3. Add the following highlighted line to the onCreate
method of your NewActivity
class:
public void onCreate(Bundle icicle){
super.onCreate(icicle);
setContentView(R.layout.activity_new);
}
setContentView
is the method in which you tell Android which XML file to display for your new activity. Now that you’ve created a new class, string, and layout file, it’s time to launch the activity and display your new view on the screen.
The simplest way to launch your new activity on a device is to touch it. Launching an activity just by touching the screen probably isn’t a very common use case in practice, but it makes for a good and simple example. Most new activities are started when a user selects a list item, presses a button, or takes another action with the screen.
Inside your MainActivity (the one that was auto-generated for you when you made the project), add the following code :
public class MainActivity extends Activity {
public void onCreate(Bundle icicle){
super.onCreate(icicle);
setContentView(R.layout.activity_new);
View view = getWindow().getDecorView()
.findViewById(android.R.id.content);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Launch new Activity here!
}
});
}
}
Important: After you add this code, in Eclipse choose Source > Organize Imports; in Android Studio, choose Code > Optimize Imports. If your IDE is confused about which OnClickListener
to import, select View.OnClickListener
(not Dialog.OnClickListener
). Organizing your imports is something that you will need to do on a fairly regular basis while building applications, so this one might be worth learning the shortcuts for. Check out the Source menu in Eclipse or the Code menu in Android studio; the keyboard shortcuts will be listed next to the name.
By setting an OnClickListener
on the view, you are letting the system know that you want a callback if this view is clicked. Don’t worry about the getWindow()
and getDecorView()
functions for now; they are just a quick way of catching clicks on the entire screen. Later on I’ll show you how to set these click listeners on actual buttons and other views.
Finally, it’s time to launch the new activity. This will start your brief foray into intents. Each new activity is started as the result of a new intent being dispatched into the system (which processes it and takes the appropriate action). To start the first activity, you’ll need a reference of your application context and the class object for your new activity. Let’s create the new intent first.
1. Place the following code into your onClick
method:
Intent startIntent=new Intent(MainActivity.this,
NewActivity.class);
You will probably need to organize your imports after adding the Intent
. You’re passing the new intent a context (MainActivity.this
is an easy way to reference the parent activity, which itself is a subclass of Context
) and the class object of the activity that you would like to launch. This tells Android exactly where to look in the application package. There are many ways to create and interact with intents; this is, however, the simplest way to start up a new activity.
2. Once the intent is properly constructed, it’s simply a matter of telling the Android system that you’d like to start the new activity. Put the following line into your onClick
method after the intent:
startActivity(startIntent);
Your OnClickListener
should now look like the following:
View view = getWindow().getDecorView()
.findViewById(android.R.id.content);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent startIntent = new Intent(MainActivity.this, NewActivity.class)
startActivity(startIntent);
}
});
Throughout this whole process, the original activity has never once had access to the instance of the new activity. Any information that might pass between these two activities must go through the intermediary intent. You’ll learn how this is done in the section “The Intent Class.”
If you’re running your IDE and you’ve been coding along with me, it should now be a simple matter of spinning up the emulator and installing your new activity (the steps for which you should remember from Chapter 1). Once your new application has launched, press the center key to see the results of your labor (Figure 2.1).
Now that you know how to create and launch a new activity, it’s time to discuss how that process works. You’ll need to understand, for the purposes of UI layout and data management/retention later, what methods are called each time one of your activities makes its way onto, and off of, the screen.
Each activity lives a very short but exciting life. It begins when an intent that your activity is registered to receive is broadcast to the system. The system calls your activity’s constructor (while also starting your application as necessary) before invoking the following methods on the activity, in this order:
onCreate
onStart
onResume
When you implement an activity, it’s your job to extend the methods that make up this lifecycle. The only one you are required to extend is onCreate
. The others, if you declare them, will be called in the lifecycle order.
Your activity is the top visible application, can draw to the screen, will receive key events, and is generally the life of the party. When the user presses the Back key from the activity, these corresponding methods are called in the following order:
onPause
onStop
onDestroy
After these methods have executed, your activity is closed down and should be ready for garbage collection.
In order to make sense of the activity’s flow, let’s quickly look over each lifecycle method in detail. Remember that you must evoke the superclass call in each of these methods (often before you do anything else) or Android will throw exceptions at you.
Note
onCreate
is the only one of the application lifecycle methods that you must implement. I usually end up implementing only one or two of these methods, depending on what each activity is responsible for.
Android will call your declaration of the onCreate
method as your activity is starting, and it will be called only once per activity lifecycle. Remember that if the user rotates the device, the activity will be destroyed and re-created, at which point onCreate
would be called again, but that’s because it’s a new activity.
The onCreate
method is the place to do things you want to do only once. For example, if the title for your activity is dynamic but will not change after the activity is started, onCreate
would be where you’d want to reach into the view hierarchy and set up the title. This method is not the place to configure data that could change while the app is in the background or when another activity is launched on top of it—that we’re saving for onResume.
Further, if your app is running in the background and the system is running low on resources, your application may be killed. If that happens, the onCreate
method will be called on a new instance of the same activity when your application returns to the foreground.
The onCreate
method is also your one and only chance to call setContentView
for your activity. This, as you saw earlier, is how you tell the system what layout you’d like to use for this screen. You call setContentView
once you can begin setting data on the UI. This could be anything from setting the contents of lists to TextView
s or ImageView
s.
When starting up, your onStart
method is called immediately after onCreate
. If your app was put in the background (either by another application launching over yours or because the user pressed the Home button), onStart
will be called as you resume but before the activity can interact with the screen. I tend to avoid overriding onStart
unless there’s something specific I need to check when my application is about to begin using the screen.
onResume
is the last method called in the activity lifecycle as your activity is allowed access to the screen. If UI elements changed while your activity was in the background, this method is the place to make sure the UI and phone state are in sync.
When your activity is starting up, this method is called after onCreate
and onStart
. When your activity is coming to the foreground again, regardless of what state it was in before, onResume
will always be called. This is a great place to make network calls to make sure that data is refreshed every time you come back to this activity.
After all this setup, configuration, and work, your activity is now visible to the user. Things are being clicked, data may be parsed and displayed, lists are scrolled, and things are happening! At some point, however, the party must end (perhaps because the user pressed the Back key), and you’ll need to wind things down.
onPause
is the first method called by the system as your application is leaving the screen. If you have any processes or loops (animations, for example) that should be running only while your activity is onscreen, the onPause
method is the perfect place to stop them. onPause
will be called on your activity if you’ve launched another activity over the one you’re currently displaying.
Keep in mind that if the system needs resources, your process could be killed anytime after the onPause
method is called. This isn’t a normal occurrence, but you need to be aware that it could happen.
The onPause
method is important because it may be the only warning you get that your activity (or even your entire application) is going away. It is in this method that you should save any important information to disk, your database, or the preferences.
Once your activity has actually left the screen, you’ll receive the next call in the activity lifecycle.
When Android calls your onStop
method, it indicates that your activity has officially left the screen. Further, onStop
is called when the user is leaving your activity to interact with another one. This doesn’t necessarily mean that your activity is shutting down (although it could). You can only assume that the user has left your activity for another one. If you’re doing any ongoing processing from within your activity that should run only while it’s active, this method is your chance to be a good citizen and shut it down.
onDestroy
is your last method call before oblivion. This is your last chance for your activity to clean up its affairs before it passes on to the great garbage collector in the sky.
Any background processes that your activity may have been running in the background (fetching/parsing data, for example) must be shut down on this method call.
However, just because onDestroy
is called doesn’t mean that your activity will be obliterated; it just marks the application as ready to be cleaned up so that whenever the garbage collector requires more resources, it knows that it is allowed to garbage-collect this application. So if you have a thread running and downloading data in the background, it may continue to run and take up system resources even after the onDestroy
method is called unless you explicitly cancel it here.
As mentioned, your process can be killed at any point after onPause
if the system needs resources. The user, however, shouldn’t ever know that this culling has occurred. Android gives you two chances to save your state data for later use.
This method passes you a bundle object into which you can put any data that you’d need to restore your activity to its current state at a later time. You’ll do this by calling something like outState.putString(...)
or outState.putBoolean(...)
. Each stored value requires a string key going in, and it requires the same string key to come back out. You are responsible for overriding your own onSaveInstanceState
method. If you’ve declared it, the system will call it; otherwise, you’ve missed your chance.
When your previously killed activity is restored, the system will call your onCreate
method again and hand back to you the bundle you built with onSaveInstanceState
.
onSaveInstanceState
will be called only if the system thinks you may have to restore your activity later—for instance, if you received a phone call. It wouldn’t be called if, for example, the user has pressed the Back key, as the device clearly has no need to resume this exact activity later. As such, this method is not the place for saving user data. Only stash temporary information that is important to the UI on this particular instance of the screen.
When the user switches between portrait and landscape mode, your activity is destroyed and a new instance of it is created (going through the full shutdown-startup cycle of method calls). When your activity is destroyed and created specifically because of a configuration change (the device rotation being the most common), onRetainNonConfigurationInstance
gives you a chance to return any object that can be reclaimed in your new activity instance by calling getLastNonConfigurationInstance
. This is great for saving large amounts of data, like images or big network requests, that you wouldn’t normally be able to serialize into the outState
from onSaveInstanceState
.
This tactic helps make screen rotation transitions faster. Keep this in mind if it takes your activity a significant amount of time to acquire data that it plans to display on the screen. Instead, you can get the previously displayed data by using getLastNonConfigurationInstance
.
Here’s an example of how to use onRetainNonConfigurationInstance
:
@Override
public Object onRetainNonConfigurationInstance() {
final DataModel data = getFetchedData();
return data;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
DataModel data = (DataModel) getLastNonConfigurationInstance();
if (data == null) {
data = fetchData();
}
}
You should now have a basic understanding of
Steps for creating a new activity
How an activity is started
The lifecycle of an activity
You have what you need to keep up as I go over more complex topics in later chapters. Fear not, I’ll come back to the activity in no time.
Intents, in the Android platform, make up the major communication protocol for moving information between application components. In a well-designed Android application, components (activity, content provider, or service) should never directly access an instance of any other component. As such, intents are how these pieces are able to communicate.
A good half of this book could be dedicated to the creation, use, and details of the Intent
class. For the sake of brevity and getting you up and running as fast as possible, I’ll cover only a few basics in this chapter. Look for intents throughout the rest of this book. They’re probably the most-used class in Android as a whole.
There are two main ways to tell the Android system that you’d like to receive intents sent out by the system, by other applications, or even by your own app:
Registering an <intent-filter>
in the AndroidManifest.xml
file
Registering an IntentFilter
object at runtime with the system
In each case, you need to tell the Android system what events you want to listen for.
There are numerous ways of sending intents as well. You can broadcast them out to the system as a whole, or you can target them to a specific activity or service. However, to start a service or activity, it must be registered in the manifest (you saw an example of this in the previous demonstration on starting a new activity).
Let’s take a look at how to use intents in practice.
Why not register everything at runtime? If an intent is declared as part of your manifest, the system will start your component so that it will receive it. Registration at runtime presupposes that you are already running. For this reason, anytime you want your application to awaken and take action based on an event, declare it in your manifest. If it’s something your application should receive only while it’s running, register an IntentFilter
(it’s an intent-filter
when declared in XML, but an IntentFilter
in your Java code) once your particular component has started.
Let’s go back to the initial application and look again at the activity’s entry in the manifest:
<activity android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
The android.intent.action.MAIN
declaration tells the system that this activity is the main activity for your application. No parameters are needed to start it. It’s a good idea to list only one activity as MAIN
in the manifest. This is also how ADB (the Android Debug Bridge) knows which activity to start up.
The android.intent.category.LAUNCHER
category tells the system that the enclosing activity should be launched when your icon is clicked on the phone’s application dock. Further, it tells Android that you’d like the icon to appear in the app launcher drawer. This is an example of an intent-filter
that’s created for you by Android’s project creation tools. Let’s add one of our own.
If you skipped the previous section about the Activity
class, now may be a good time to go back and at least skim over the code. In that section, I showed you how to declare and launch a simple new activity. What I didn’t show you, however, was how to make that activity accessible to the system as a whole by declaring an <intent-filter>
for it within your manifest. Let’s do that now.
1. Add an intent-filter
to the NewActivity
declaration:
<activity android:name=".NewActivity">
<intent-filter>
<action android:name="com.peachpit.PURPLE_PONY_POWER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
In this code, you’ve registered for intents containing the com.peachpit.PURPLE_PONY_POWER
action and set the intent-filter
category to default.
Now, lest you think I’m a crazed children’s toy enthusiast, I’ve used this rather absurdist action string to demonstrate a point—namely, that the only requirement for the action string is that it be unique for your particular component.
In the previous section, I showed you how to launch the new activity by using the following lines:
Intent startIntent = new Intent(MainActivity.this, NewActivity.class);
startActivity(startIntent);
This works, but it has one major limitation: It cannot be launched outside your own application’s context. This renders useless one of the most powerful features that the activity-intent model has to offer. Namely, any application on the device, with the right intent, can use components within your application.
Now that you’ve added the <intent-filter>
to the sample project manifest, you can launch this particular activity anywhere with the following code:
Intent actionStartIntent = new Intent("com.peachpit.PURPLE_PONY_POWER");
startActivity(actionStartIntent);
Notice a very important difference between this code and the listing above it. When you create the intent in this example, you’re not required to pass in a Context
object (the bundle of information that is required to communicate with the system at large). This allows any application, with knowledge of the required intents, to start the NewActivity
class.
2. In your OnClickListener
, replace your previous intent code with this new action intent code that’s highlighted below. It should look like this:
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent actionStartIntent = new Intent("com.peachpit.
PURPLE_PONY_POWER");
startActivity(actionStartIntent);
}
});
Now when you press the down key in the sample application, you’ll see the same activity launching using this new manifest-declared intent-filter
.
If you’ve misspelled the intent’s action string or neglected to add the default category to your intent-filter
, you may get an android.content.Activity NotFoundException
.
This exception will be thrown by the startActivity
method anytime you create an intent that the system cannot connect to an activity listed in a manifest on the device.
Registering for intent filters is not only the purview of the activity. Any Android application component can register to be started when an intent action is broadcast by the system.
Another method for receiving events that pertain only to your application or for receiving events broadcast by the Android system itself is to listen for the intents at runtime.
Let’s say that your activity would like to show a special screen or take a custom action when the user enables Airplane mode. To do this, you’ll need to create a temporary IntentFilter
and an inner BroadcastReceiver
object instance.
Let’s add the runtime BroadcastReceiver
to the MainActivity
class. A BroadcastReceiver
is, as you can probably guess, an object with a single onReceive
method. Remember to organize your imports after adding the BroadcastReceiver
to make sure the class is recognized.
Change the MainActivity
class to look like the following:
public class MainActivity extends Activity {
private BroadcastReceiver simpleReceiver=new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(
Intent.ACTION_AIRPLANE_MODE_CHANGED)){
Toast.makeText(context,
R.string.airplane_change,
Toast.LENGTH_LONG).show();
}
}
};
//Rest of the Activity is here.
}
In this code, you are creating a locally accessible receiver for use within the activity. When the system calls onReceive
, you’ll need to check what the intent’s action is. This is a good idea, as BroadcastReceiver
could register for any number of different intents.
When you receive the event that you’re looking for, you’ll use Android’s Toast API to print a simple message on the screen (in this case, the contents of the string named airplane_change
). In practice, this might be the time to show a screen indicating that network connectivity is required for the application to run correctly.
Now that you’ve created a BroadcastReceiver
, you can register it with the system:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
registerReceiver(simpleReceiver, intentFilter);
}
This is going to be the primary way that your applications listen for information on the status of the system. Everything from the status of the battery to the status of the Wi-Fi radio is at your fingertips with this tool. You can find out more about what activities you can monitor, with what permissions, by looking in the Android SDK documentation for the Intent
class.
For every runtime registration that you create, you must also unregister it. If you would like to receive the events only when your activity is visible, onPause
is the best place to turn off the receiver. If you’d like to listen for as long as your activity is running, even if it’s not visible, you’ll want to unregister in onDestroy
. Wherever you decide to stop listening, simply call unregisterReceiver
(a method implemented by your superclass) and pass in the BroadcastReceiver
you created earlier, like this:
@Override
public void onDestroy(){
super.onDestroy();
unregisterReceiver(simpleReceiver);
}
Again, this is all in the original MainActivity
class made by the SDK when you created the project.
1. In the onCreate
method, create an intent filter and add the action Intent.ACTION_AIRPLANE_MODE_CHANGED
to it.
2. Add as many actions to this intent filter as you wish. When your receiver is called, you’ll have to sort out which intent actually triggered the onReceive
method for the BroadcastReceiver
by calling getAction()
on the intent.
3. Test the code by holding the power button down; this will pop up a dialog with several options.
4. Enable Airplane mode. If you’ve done everything right so far, you should see a small message pop up along the bottom of the screen with your alert message in it.
5. Clean up by unregistering the BroadcastReceiver
in onDestroy
.
You may be thinking to yourself “Self, what happens when more than one activity is registered for the same intent?” This is a very interesting question, one that Android resolves simply by asking the user.
If two activities listen for the same intent in their manifests, and an application attempts to start an activity with that intent, the system will pop up a menu giving users a list of possible applications to choose from (Figure 2.2).
You’ve probably seen similar behavior hundreds of times on your desktop computer, such as when opening a file and being given a choice about which application you’d like to open it with.
This notion of many activities registering for the same intent can have delightful side effects. In Android, any application can register to share media with a given MIME time by using the android.intent.action.SEND
action.
Figure 2.3 is what the Share tab on my phone looks like when I press it in the image gallery.
It is this ability to register for similar intents that allows seamless interaction, as each application registering this intent is given an entry on the Share menu. Clicking an entry in this list will start the registered activity and pass along as an extra the location at which the image can be accessed. What is an extra? Good question.
One of the major features of the intent is the ability to package and send data along with it. One activity should never directly manipulate the memory of another. However, they still must have a way to communicate information. This communication is accomplished with the help of the intent’s Extra
bundle. The bundle can hold any number of string-primitive pairs. Perhaps the best way to illustrate this concept is with some code and an example.
Earlier, I showed you how to start a new activity by using an action-based broadcast intent.
Add the following highlighted code to the OnClickListener
that you’ve dealt with before:
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent actionStartIntent = new
Intent("com.peachpit.PURPLE_PONY_POWER");
actionStartIntent.putExtra("newBodyText",
"You touched the screen!");
startActivity(actionStartIntent);
}
});
You’re adding a string payload to the intent before using it to start an activity. Whoever receives the intent will be able to pull this string out (assuming they know it’s there) and use it as they see fit. Now that you know how to attach the data, let’s take a look at an example of retrieving and using the string in NewActivity
’s onCreate
method:
public void onCreate(Bundle icicle){
super.onCreate(icicle);
setContentView(R.layout. activity_new);
Intent currentIntent = getIntent();
if(currentIntent.hasExtra("newBodyText")){
String newText = currentIntent.getExtras().
getString("newBodyText");
TextView bodyView = (TextView)findViewById(
R.id.new_activity_text_view);
bodyView.setText(newText);
}
In the highlighted code, I’m getting the intent that was responsible for starting my NewActivity
by calling getIntent
. Next, I’m checking if this intent actually contains the newBodyText
extra. Keep in mind that the intent may not contain the extra. If you forget to check for this case, you’ll quickly find yourself inundated with NullPointerException
s. If the extra is there, I’ll pull it out and set the string as the new text in my display. The last two lines obtain a reference to the screen’s text view and change the text to be the contents of the extra. Don’t worry about the mechanics of that particular operation right now; you’ll learn about this topic in depth later.
You’ve learned how to register for, create, and use the basic functionality of an intent. As you now know, they can be registered for in the manifest or at runtime. They can be sent by any application on the phone, and any number of application components can register for the same intent.
The goal in this section was to get you started on the care and feeding of Android intents. In future chapters and tasks, you’ll work with intents again in many different contexts.
Typically, an Android application is a collection of activities, broadcast receivers, services, and content providers. The Application
class is the glue that binds all these disparate pieces into a singular, unified entity. Every time a content provider, activity, service, or intent receiver in your manifest is initialized, an Application
class is also spun up and available to it.
Looking in the AndroidManifest.xml
file, you’ll see a typical Application
declaration that looks like the following:
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<!—Activities, Services, Broadcast Receivers, and Content Providers -->
</application>
Here you can see the <application>
tag. This part of the manifest typically contains information relevant to your application at large. android:icon
tells the system what icon to display in the main application list. android:label
in this case refers to another entry in the strings.xml
file you were editing earlier.
Adding your own application is very similar to the steps you’ve already gone through to add a new activity.
1. Add a name field to the existing AndroidManifest.xml
declaration.
2. Create a new class in your package that extends the Application
class.
3. Profit!
Let’s go over steps 1 and 2 in depth. You’re on your own for step 3.
When it comes to the manifest, android:name
refers not to the name of the object being described, but to the location of the class in your Java package. The Application
declaration is no exception. Here’s what the opening tag of the application should look like with the new declaration:
<application android:icon="@drawable/icon"
android:label="@string/app_name"
android:name= ".SampleApplication">
In this declaration, you tell the system what icon you want to represent your application on the Android application drawer.
Once again, the class loader will look for your Application
class by appending the contents of android:name
to the end of your package declaration within the <manifest>
opening tag. Now you’ll need to actually create this class to keep the class loader from getting unhappy.
Here’s what you’ll need, at a very basic level, to have an Application
of your very own:
import android.app.Application;
public class SampleApplication extends Application{
public void onCreate(){
super.onCreate();
}
}
The Application
can be a very simple class. It’s hard to understand what the Application
can do for you until you consider a few things:
Activities are very transient.
Activities have no access to each other’s memory, and they should communicate through intents.
Because activities are constantly being stopped and started for a variety of reasons, there’s no way for your activity to know if it’s being started for the very first time in the run of your application. The Application
class’s onCreate
method, on the other hand, is called only when your app is being initialized. As such, it can be a good place to take actions that should happen only when your application is first started.
If you need a temporary holding pen for data that may span many activities, a data member that’s part of the Application
can be a convenient place to store it. You must be very careful about adding data to the Application
. Any single component declared in your manifest, from the simplest BroadcastReceiver
to the most complex activity, will, before it’s created by the system, first create your Application
object. This means you must make the Application
’s onCreate
method run as fast as you possibly can.
All your broadcast receivers, services, activities, and content providers have a method named getApplication
provided to them by the appropriate superclass. When invoked, getApplication
will return a reference to your Application
object if you specified one in the manifest. Getting access to it, now that you’ve declared and created the class, is as simple as calling getApplication
and casting the returned object to an instance of your own reference. Here’s what it looks like:
SampleApplication myApplication = (SampleApplication) getApplication();
That’s all there is to it. You can add public data members or context-sensitive methods to your own version of the Application
, and with one call all your components have access to the same object, like so:
public class SampleApplication extends Application{
public String username;
public void onCreate(){
super.onCreate();
}
}
To access your newly added variable, simply do the object cast listed earlier:
public void onCreate(Bundle bundle){
SampleApplication myApplication =
(SampleApplication) getApplication();
myApplication.username = "sparks";
}
Be sure that any data you put in the Application
is relevant everywhere, because the overhead for allocating and initializing the Application
can become a drag on startup times.
Over the course of this chapter, I’ve exposed you to the fundamental building blocks of an Android application. I used examples to get you started on
The manifest
Creating and using your own activities
Sending, receiving, and taking advantage of intents
Creating your own Application
object
It’s my hope that through the rest of the book, you’ll be able to use the building blocks you’ve learned in this chapter to understand how an Android application functions. From here on out, I’ll be focusing more on how to do tasks rather than on the theories that back them. On that note, let’s start making screens that include more than just a single text view.