Chapter 3
In This Chapter
Running code without bothering the user
Running code when a device starts
Starting, binding, and querying
Sending messages from one process to another
Some things are of no concern to the user of an Android device. Imagine seeing a pop-up message saying, “A process on your phone is checking for email right now,” followed immediately by the message, “Nope, no new email. Sorry about the interruption. Get back to what you were doing. You’ll hear from me again in exactly one minute.” One of my phones actually did this! Such notices are intrusive and unnecessary, especially on a device with limited screen real estate.
To do something behind the scenes, you don’t want an Android activity. An activity normally has a layout file, and the user deals with the layout’s gizmos on the screen. Instead, you want the kind of component that runs quietly in the background. In other words, you want an Android service.
I start this chapter with an embarrassingly simple example — a service that doesn’t do anything. This lazy service simply illustrates the minimum service source code requirements.
Listing 3-1 contains the good-for-nothing service.
Listing 3-1: An Un-Weather Service
package com.allyourcode.p03_03_01;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
public class MyWeatherService extends Service {
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(this, R.string.service_bound,
Toast.LENGTH_SHORT).show();
return null;
}
@Override
public int onStartCommand(Intent intent,
int flags, int startId) {
Toast.makeText(this, R.string.service_started,
Toast.LENGTH_SHORT).show();
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
Toast.makeText(this, R.string.service_destroyed,
Toast.LENGTH_SHORT).show();
}
}
In truth, the service in Listing 3-1 has more code than is absolutely necessary. As a subclass of the abstract android.app.Service
class, the only required method in Listing 3-1 is onBind
. Still, the listing’s onStartCommand
and onDestroy
methods are a bit more useful than the methods that would be inherited from the android.app.Service
class.
The required onBind
method in Listing 3-1 returns null
. Normally, the object returned by an onBind
method implements the android.os.IBinder
interface, and an object that implements IBinder
allows one process to exchange information with another process. That’s nice, but in this simple example, the service doesn’t exchange information.
I put the service from Listing 3-1 in its own Android Studio project, with its own package name. So this service runs as its own application on an emulator or a device. The service has no user interface (and, therefore, no layout file). The application’s AndroidManifest.xml
file has no <activity>
element but instead has the <service>
element shown in Listing 3-2.
Listing 3-2: The Un-Weather Service’s AndroidManifest.xml File
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.allyourcode.p03_03_01"
xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<service
android:name=".MyWeatherService"
android:enabled="true"
android:exported="true">
</service>
</application>
</manifest>
To start the service in Listing 3-1, other components refer to the name of Java class. Listing 3-3 shows you how.
Listing 3-3: A Client for the Un-Weather Service
package com.allyourcode.p03_03_03;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
public class ServiceConsumerActivity extends Activity {
Intent intent = new Intent();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intent.setClassName("com.allyourcode.p03_03_01",
"com.allyourcode.p03_03_01.MyWeatherService");
}
public void onStartClick(View view) {
startService(intent);
}
public void onStopClick(View view) {
stopService(intent);
}
}
The activity in Listing 3-3 has two buttons — a Start button and a Stop button. (See Figure 3-1.)
In creating the layout for this section’s project, I took the liberty of assigning listener method names to the two buttons:
<Button android:id="@+id/button"
android:onClick="onStartClick"
android:text="@string/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Button>
<Button android:id="@+id/button2"
android:onClick="onStopClick"
android:text="@string/stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Button>
So clicking the Start button calls startService(intent)
, and clicking the Stop button calls stopService(intent)
. In addition to starting and stopping the service, each click displays a Toast
view.
A service’s primary purposes are to run in the background (independent of any obvious user interaction) and to offer help to other apps. So, if the code in Listing 3-1 represented a useful service, this code would be doing something about the weather. (For code that does something useful, see the section “Talking about the Weather,” later in this chapter.)
Here’s what happens when you play with the buttons in Figure 3-1:
Press Start and then press Stop.
After pressing Start, you see the Service Started toast. Then, after pressing Stop, you see the Service Destroyed toast. No surprises here!
Press Stop twice in a row.
If the service is running, the first call to stopService
(in Listing 3-3) destroys the service. The second call to stopService
doesn’t display a Service Destroyed toast because a component that’s not running can’t be destroyed.
Press Start twice in a row and then press Stop twice in a row.
As a result, you see two Service Started toasts followed by only one Service Destroyed toast. Each startService
call (from Listing 3-3) triggers a call to onStartCommand
in Listing 3-1. But the first stopService
call (again, from Listing 3-3) destroys the service. Subsequent stopService
calls have no effect.
Press Start and then press the emulator’s Back button.
When you press the Back button, you don’t see a Service Destroyed toast. And that’s the essence of an Android service. A service can live on after the activity that started the service has been destroyed.
To confirm that the service is still running, use Android’s Recents list (also known as the Overview list) to restart the client activity. Then, in the client activity, don’t press the Start button. The service is still running, so simply press the Stop button. When you press the Stop button, the running service gets destroyed. So you see the Service Destroyed toast.
A service has no user interface, and it may continue to run after you destroy the service’s starting activity. That can be a dangerous combination of traits. Services without interfaces can hang around indefinitely like Rasputin — the mad monk of czarist Russia that no one could kill. If developers don’t include code to manage their services, the services clog up the system. No one’s happy.
Of course, Android can kill services in order to reclaim needed memory. The http://developer.android.com/reference/android/app/Service.html
page lists all the situations in which Android kills or doesn’t kill a service, and it doesn’t make for light reading.
One insight about the lifetime of a service comes from the onStartCommand
method in Listing 3-1. The onStartCommand
method takes an Intent
parameter. The parameter’s value points to whatever Intent
object the startService
method sends. (See Listing 3-3.) The onStartCommand
method returns an int
value. In Listing 3-1, the int
value is START_NOT_STICKY
(a constant in the android.app.Service
class). This constant value tells Android how to restart the service at a certain time interval after killing it. The alternative int
values are as follows:
START_STICKY
: If Android kills the service, Android waits for a certain time interval and then restarts the service. Upon restart, Android feeds the service the intent from whatever startService
call is next in the queue of such commands. If no startService
calls are waiting to start this particular service, Android feeds null
to the onStartCommand
method’s Intent
parameter.START_REDELIVER_INTENT
: If Android kills the service, Android waits for a certain time interval and then restarts the service. Upon restart, Android feeds the service the intent that came as a parameter in the current call to onStartCommand
.START_NOT_STICKY
: If Android kills the service, Android doesn’t automatically restart the service. Android restarts the service if and when the next startService
call queues up to start this particular service.START_STICKY_COMPATIBILITY
: If Android kills the service, Android tries to restart the service the way START_STICKY
restarts services. But Android doesn’t promise to restart the service.The bottom line is, be proactive in starting and stopping your own service. Don’t be a memory hog by relying on the system to clean up after you. Be aware of your service’s lifespan, and destroy your service when it’s no longer needed. Add a stopService
call to your activity’s onPause
or onDestroy
method if it makes sense to do so. And, if a service knows that it’s no longer useful, have the service call its own stopSelf
method.
How important is your service? Does your service start on rare occasions when the user presses a certain button? Or does your service start when the device powers up?
If Android users can’t survive without running your service, you can start the service at boot time. To do so, you create another kind of component — a broadcast receiver.
A broadcast receiver responds to intents that you fling into the air using the sendBroadcast
or sendOrderedBroadcast
method. Android provides special treatment for an intent sent with either of these methods.
When you call startActivity
or startService
, Android looks for one component to satisfy the intent.
If the system finds more than one suitable activity (two installed web browsers, for example), Android displays a dialog box prompting the user to choose among the alternatives.
When you call sendBroadcast
or sendOrderedBroadcast
, Android fires up all the receivers whose filters satisfy the intent.
With sendOrderedBroadcast
, Android runs receivers one after the other. Each receiver can pass the intent on to the next receiver in line or can break the chain by calling its abortBroadcast
method.
With sendBroadcast
, Android may interleave the running of several receivers. In this scenario, having a receiver abort a broadcast doesn’t make sense.
For an example, consider the Weather service in Listing 3-1. In the same application, create a Java class with the code from Listing 3-4.
Listing 3-4: A Simple Broadcast Receiver
package com.allyourcode.p03_03_01;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class MyBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent serviceIntent = new Intent();
serviceIntent.setClassName(
"com.allyourcode.p03_03_01",
"com.allyourcode.p03_03_01.MyWeatherService");
context.startService(serviceIntent);
}
}
When MyBootReceiver
runs, it starts an instance of the MyWeatherService
class. The not-too-difficult trick is to make MyBootReceiver
run when the emulator or device starts.
Listing 3-5 shows you the mechanics of launching the receiver in Listing 3-4.
Listing 3-5: Manifest for the Receiver in Listing 3-4
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.allyourcode.p03_03_01"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name=
"android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<service
android:name=".MyWeatherService"
android:enabled="true"
android:exported="true">
</service>
<receiver
android:name=".MyBootReceiver"
android:enabled="true"
android:exported="true" >
<intent-filter>
<action android:name=
"android.intent.action.BOOT_COMPLETED" />
<category android:name=
"android.intent.category.HOME" />
</intent-filter>
</receiver>
</application>
</manifest>
The <uses-permission>
element in Listing 3-5 grants this app permission to receive "android.intent.action.BOOT_COMPLETED"
broadcasts. And, in the receiver’s <action>
element, the android:name
attribute says, “Wake me up if anyone hollers android.intent.action.BOOT_COMPLETED
".
When you launch your emulator or you turn on your device, Android runs through its normal boot sequence and then sends an intent containing ACTION_BOOT_COMPLETED
. At that point, Android finds the receiver in Listing 3-4 and calls the receiver’s onReceive
method. In turn, the onReceive
method in Listing 3-4 gooses the Weather service in Listing 3-1.
You can download this section’s example from the book’s website. To test the code, install the app on an emulator. Then shut down the emulator and restart the emulator. Ay, there’s the rub! Starting an emulator once is annoying enough. Starting it several times (because you got some detail wrong the first few times) is a pain in the class.
Your code can’t test Listing 3-4 by creating an ACTION_BOOT_COMPLETED
intent. Android reserves ACTION_BOOT_COMPLETED
for system-level code only. By using the Android Debug Bridge, though, you can launch an intent as a Linux shell superuser. When you do, your emulator or device does what looks like a partial reboot, and this saves a bit of time. Here’s what you do:
In the Terminal window, issue the cd
command to make the ANDROID_HOME/platform-tools
directory your working directory.
For examples of the use of the cd
command, and for help finding your ANDROID_HOME/platform-tools
directory, see Book I, Chapter 2.
Type the following command (but type it all on one line):
adb shell am broadcast
-a android.intent.action.BOOT_COMPLETED
Using Android’s am
command, you can call startActivity
, startService
, and sendBroadcast
as if you were Android itself (or himself, or herself, or whomever). When you issue the command in Step 4, Android behaves as if the system is just finishing its boot sequence.
Using Run ⇒ Run ‘app' or the Recents button on the emulator, restart the client app (from Listing 3-3).
When the app starts running, don’t touch the Start button. You’re trying to find out if the service is already running.
In the client app’s activity, press the Stop button.
Hooray! The Service Destroyed message appears on your emulator’s screen.
You can do two kinds of things with a service:
You can start and stop a service.
You do this by calling the Context
class’s startService
and stopService
methods. Also, a service can take the bull by the horns and call its own stopSelf
or stopSelfResult
method.
When you call startService
, you create only a momentary relationship with the service. Android creates an instance of the service if no instances are already running. In addition, Android calls the service’s onStartCommand
method. (See Listing 3-1.)
Calls to startService
don’t pile up. To illustrate the point, consider this sequence of method calls, along with their resulting Android responses:
Activity A calls startService to start MyService.
Android instantiates MyService and
calls the instance’s onStartCommand method.
Activity B calls startService to start MyService.
Android calls the existing instance’s
onStartCommand method.
Activity A calls stopService to stop MyService.
Android destroys the MyService instance.
Activity B calls stopService to stop MyService.
Android says "The joke’s on you." There’s no
instance of MyService to stop.
You can bind to and unbind from a service.
You do this by calling the Context
class’s bindService
and unbindService
methods. Between binding and unbinding, you have an ongoing connection with the service. Through this connection, you can send messages to the service and receive messages from the service. That’s useful!
When you call bindService
, Android creates an instance of the service if no instances are running already. In addition, Android calls the service’s onBind
method. (For an example, skip ahead to Listings 3-6 and 3-7.)
When you call bindService
, Android doesn’t call the service’s onStartCommand
method.
Calls to bindService
pile up. A service can have many bindings at once, each to a different activity. Your service’s code can keep track of all this hubbub by maintaining a collection of binding objects (an ArrayList
, or whatever). When you call unbindService
, you don’t destroy the service instance. Android keeps the service alive as long as any activities are bound to the service.
Services can receive Start requests and Bind requests all at the same time. When all bound activities unbind themselves from a particular service, the system checks whether anybody started the service this time around. If so, the system waits for somebody to call stopService
before destroying the service.
Android can terminate activities to reclaim memory. If I were a service and Android terminated the activities that were bound to me, I’d be afraid for my own survival. Test your apps for unwanted results from the untimely termination of activities and their services. If, in testing, you experience any unexpected behavior due to the early termination of a service, please fix the code.
The previous sections' examples started and stopped a service. The rest of this chapter binds and unbinds with a service.
Every Android book needs a Weather Service example, and this book is no exception. In this section, your activity binds to a service, which in turn reaches out for weather information over the Internet.
I build the example in stages. The first stage is essence de service. An activity binds to the service, gets back some fake responses to nonsense queries, and then unbinds. Listing 3-6 contains the service.
Listing 3-6: A Weather Service with a Fear of Commitment
package com.allyourcode.p03_03_06;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.Toast;
public class MyWeatherService extends Service {
Messenger messengerToClient = null;
MyIncomingHandler myIncomingHandler =
new MyIncomingHandler();
Messenger messengerToService =
new Messenger(myIncomingHandler);
@Override
public IBinder onBind(Intent intent) {
doToast(R.string.service_bound);
return messengerToService.getBinder();
}
class MyIncomingHandler extends Handler {
@Override
public void handleMessage(Message incomingMessage) {
messengerToClient = incomingMessage.replyTo;
Bundle reply = new Bundle();
reply.putString("weather", "It’s dark at night.");
Message replyMessage = Message.obtain();
replyMessage.setData(reply);
try {
messengerToClient.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
doToast(R.string.message_handled);
}
}
@Override
public boolean onUnbind(Intent intent) {
doToast(R.string.service_stopped_itself);
stopSelf();
return false;
}
@Override
public void onDestroy() {
myIncomingHandler = null;
doToast(R.string.service_destroyed);
}
void doToast(int resource) {
Toast.makeText(this, resource,
Toast.LENGTH_SHORT).show();
}
}
The flow of control in Listing 3-6 isn’t simple, so I created Figure 3-2 to help you understand what’s going on. The first thing to notice in Figure 3-2 is that the service doesn’t interact directly with a client application. Instead, the service gets calls indirectly through the Android operating system.
Like many other communication regimens, the talk between a client and a service has two phases:
To bind to a service, a client sends an intent. Android hands this intent to the service’s onBind
method. In response, the onBind
method returns a binder — an object that implements the IBinder
interface. (Refer to Listing 3-6 and Figure 3-2.) The binder is like a business card. By returning a binder, the service says, “Android, tell the client application that it can reach me at this address.” That’s why, in Listing 3-6, the service creates the binder from an instance of MyIncomingHandler
. (It’s the same as printing a business card from an instance of “my answering machine’s phone number.")
Android delivers the binder to the client app. Eventually, the client app queries the service. The query contains a request for specific information. But the query also contains a replyTo
field. The service’s inner class (in this example, MyIncomingHandler
) uses the replyTo
information to send an answer back to the client app. In Listing 3-6, I keep things simple by replying It’s dark at night
no matter what query the client sends. (A weather report like this is always correct.)
Listing 3-7 contains a client for the service in Listing 3-6. The first several lines of Listing 3-7 are parallel to the code in Listing 3-6. Like the service in Listing 3-6, the client in Listing 3-7 has messengers and an incoming handler class. But unlike the service in Listing 3-6, the client in Listing 3-7 spans three printed pages. So, in this section, I describe Listing 3-7 one piece at a time.
Listing 3-7: A Client for the Service in Listing 3-6
package com.allyourcode.p03_03_07;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class ServiceConsumerActivity extends Activity {
Messenger messengerToService = null;
MyIncomingHandler myIncomingHandler =
new MyIncomingHandler();
Messenger messengerFromService =
new Messenger(myIncomingHandler);
Serviceconnection connection =
new MyServiceConnection();
SharedPreferences prefs;
boolean isBound = false;
void bind() {
Intent intent = new Intent();
intent.setClassName("com.allyourcode.p03_03_06",
"com.allyourcode.p03_03_06.MyWeatherService");
isBound =
bindService(intent, connection,
Context.BIND_AUTO_CREATE);
}
public void queryService() {
if (isBound) {
Bundle bundle = new Bundle();
bundle.putString("location", "19122");
// 19122 is the zip code of the neighborhood
// where I grew up. Philadelphia’s El train
// passed right by my bedroom window.
Message message = Message.obtain();
message.replyTo = messengerFromService;
message.setData(bundle);
try {
messengerToService.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
textView.setText(R.string.report_appears_here);
doToast(R.string.service_not_bound);
}
}
class MyIncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
Bundle bundle = msg.getData();
textView.setText(bundle.getString("weather"));
}
}
void unbind() {
if (isBound) {
unbindService(connection);
isBound = false;
}
}
class MyServiceConnection implements ServiceConnection {
public void onServiceConnected(
ComponentName className, IBinder binder) {
messengerToService = new Messenger(binder);
doToast(R.string.service_connected);
}
public void onServiceDisconnected(ComponentName n) {
messengerToService = null;
doToast(R.string.service_crashed);
}
}
void doToast(int resource) {
Toast.makeText(this, resource,
Toast.LENGTH_SHORT).show();
}
@Override
public void onDestroy() {
super.onDestroy();
prefs = getSharedPreferences("PREFS", MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("isBound", isBound);
editor.putString("report", textView.getText()
.toString());
editor.commit();
unbind();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_service_consumer);
prefs = getSharedPreferences("PREFS", MODE_PRIVATE);
if (prefs != null) {
textView = (TextView) findViewById(R.id.textView);
textView.setText(prefs.getString("report",
getString(R.string.report_appears_here)));
if (prefs.getBoolean("isBound", false)) {
bind();
}
}
bindButton = (Button) findViewById(R.id.buttonBind);
locationText =
(EditText) findViewById(R.id.editText);
queryButton = (Button) findViewById(R.id.buttonQuery);
unbindButton =
(Button) findViewById(R.id.buttonUnbind);
}
public static TextView textView;
Button bindButton, queryButton, unbindButton;
EditText locationText;
public void onButtonClick(View view) {
if (view == bindButton) {
bind();
} else if (view == queryButton) {
queryService();
} else if (view == unbindButton) {
unbind();
}
}
}
Figure 3-3 shows a layout that I created for the activity in Listing 3-7. The bind
, queryService
, and unbind
methods in Listing 3-7 handle the button clicks in Figure 3-3. (This section’s service doesn’t care what city you’re in, so, in Figure 3-3, the EditText view set up for a United States postal code doesn’t serve any purpose. It’s a placeholder for user input in subsequent examples.)
In the activity’s layout file, I set each button’s onClick
attribute to onButtonClick
. Then, in the Java code (Listing 3-7), the onButtonClick
method tests to find out which of the three buttons experienced the click event.
Near the bottom of Figure 3-3, there’s a capsule-shaped pop-up containing the words Message handled. The rectangle illustrates the use of Android’s Toast
class. A toast is an unobtrusive little view that displays some useful information for a brief period of time. A Toast
view pops up on the screen, the way a hot piece of bread pops up from a toaster. (Rumor has it that the Android class name Toast
comes from this goofy analogy.)
The Toast
class has two extremely useful methods: makeText
and show
.
The static Toast.makeText
method creates an instance of the Toast
class.
The makeText
method has three parameters:
this
in Listing 3-7).The second parameter is either a resource or a sequence of characters (a String
, for example).
If you call makeText
with a String
, the user sees the String
when Android displays the toast. If you call makeText
with a resource, Android looks for the resource in your app’s res
directory. In Listing 3-7, the code calls makeText
twice — once with resource R.string.service_connected
and once with R.string.service_crashed
.
If you use an int
value (42, for example) for the second parameter of the makeText
method, Android doesn’t display the characters 42 in the Toast
view. Instead, Android looks for a resource whose value in R.java
is 42. Your R.java
file probably doesn’t contain the number 42. So, instead of a Toast
view, you get a ResourceNotFound
exception. Your app crashes, and you groan in dismay.
makeText
method’s third parameter is either Toast.LENGTH_LONG
or Toast.LENGTH_SHORT
. With LENGTH_LONG
, the Toast
view appears for about four seconds. With LENGTH_SHORT
, the Toast
view appears for approximately two seconds.The show
method tells Android to display the Toast
view.
In Listing 3-7, notice that I call both makeText
and show
in one Java statement. If you forget to call the show
method, the Toast
view doesn’t appear. You stare in disbelief wondering why you don’t see the Toast
view. ("Who stole my toast?” you ask.) When you finally figure out that you forgot to call the show
method, you feel foolish. (At least that’s the way I felt when I forgot earlier today.)
In Listing 3-7, the call to bindService
takes three parameters — an intent, a service connection, and an int
value representing flags.
The intent helps determine which service to invoke.
In Listing 3-7, the intent has class name com.allyourcode.p03_03_06.MyWeatherService
. That points to the code in Listing 3-6.
The connection is the virtual rope between the client and the service.
The connection
parameter in Listing 3-7 implements the android.content.ServiceConnection
interface. I declare the MyServiceConnection
class later in Listing 3-7.
Notice that in one of the MyServiceConnection
class’s methods, Android hands the service’s business card (the binder) to the client. This is a bit different from the code in Listing 3-6, where the service gets replyTo
information from each incoming message. The difference stems from the way the client and the service talk to each other. The client initiates communications, and the service twiddles its virtual thumbs waiting for communications.
Another thing to notice about MyServiceConnection
is the peculiar role of the onServiceDisconnected
method. As the toast implies, Android doesn’t call onServiceDisconnected
unless the service takes a dive prematurely.
The flags provide additional information about the run of the service.
When Android needs more memory, Android terminates processes. In Listing 3-7, the flag BIND_AUTO_CREATE
tells Android to avoid terminating the service’s process while your activity runs. An alternative, BIND_NOT_FOREGROUND
, tells Android not to consider your activity’s needs when deciding whether to terminate the service’s process.
In Listing 3-7, the queryService
method asks the service for the answer to a question. Here’s what the queryService
method does:
queryService
method obtains a blank message from the android.os.Message
class.queryService
method adds a question (the bundle) to the message.queryService
method tells a messenger to send the message to the service.A bundle (an instance of android.os.Bundle
) is something that a process can write to and that another process can read from. You see a bundle in every activity’s onCreate
method. In the world of data communications, sending a message is likened to writing data. So, in Listing 3-7, the code juggles bundles.
queryService
method puts a bundle on a message and then “writes” the message to an Android message queue.handleMessage
method in the MyIncomingHandler
class “reads” a message from an Android message queue and then gets the message’s bundle for display on the device’s screen.Listing 3-7 contains an important lesson about the life of a service. A service that’s bound to another component (an activity, for example) tends to stay alive. If developers don’t explicitly unbind from services, the services build up and start clogging Android’s pipes. So a good citizen does the housekeeping to unbind services.
So, in Listing 3-7, the onDestroy
method unbinds the service. So far, so good. But what happens when the user turns the device sideways? Chapter 1 of this minibook reminds you what happens when the user reorients the device — Android destroys the current activity and re-creates the activity in the new orientation. So, if you’re not careful, the user loses the service just by turning the device sideways. That’s probably not what you want.
To defend against this problem, use shared preferences. With shared preferences, you can store information. Later, your app (or, if you want, someone else’s app) can retrieve the information.
Here’s how you wield a set of shared preferences:
To create shared preferences, call the android.content.Context
class’s getSharedPreferences
method.
For parameters, feed a name and a mode to the method call. In Listing 3-7, the name is the string "PREFS"
, and the mode is the int
value android.content.Context.MODE_PRIVATE
. The alternatives are
MODE_PRIVATE
: No other process can read from or write to these preferences.MODE_WORLD_READABLE
: Other processes can read from these preferences.MODE_WORLD_WRITEABLE
: Other processes can write to these preferences.MODE_MULTI_PROCESS
: Other processes can write to these preferences even while a process is in the middle of a read operation. Weird things can happen with this much concurrency. So watch out!You can combine modes with Java’s bitwise or
operator. So a call such as
getSharedPreferences("PREFS",
MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);
makes your preferences both readable and writable for all other processes.
To add values to a set of shared preferences, use an instance of the android.content.SharedPreferences.Editor
class.
In Listing 3-7, the onDestroy
method creates a new editor object. Then the code uses the editor to put information for safe keeping into the shared preferences. Each piece of information is a (
name
,
value
)
pair, such as ("isBound", isBound)
or ("report", textView.getText().toString())
. For putting information into shared preferences, the Editor
class has methods such as putInt
, putString
, putStringSet
, and so on.
To make Android remember the information in shared preferences, call the editor’s commit
method.
Again, see Listing 3-7.
getSharedPreferences
, using the same name as the name you used to create the preferences. Can you guess which listing contains an example of this code? Yes! Listing 3-7. Look at the listing’s onCreate
method.To read values from an existing set of shared preferences, call getBoolean
, getInt
, getFloat
, or one of the other get
methods belonging to the SharedPreferences
class.
In Listing 3-7, the call to getBoolean
takes two parameters. The first parameter is the name in whatever (
name
,
value
)
pair you’re trying to get. The second parameter is a default value. So, when you call prefs.getBoolean("isBound", false)
, if prefs
has no pair with name "isBound"
, the method call returns false
.
In Listing 3-7, the onDestroy
method saves the value of isBound
. Then, when Android revives the activity, the onCreate
method retrieves the isBound
value. In effect, the onCreate
method “finds out” whether the service was bound before the activity was destroyed. If the service was bound, the code renews its connection, making another call to the bindService
method.
In this section, you supplement the code in Listings 3-6 and 3-7 so that your app retrieves real weather data. Here’s what you do:
In the client app, get the user’s input from the EditText widget and send this input to the service.
That is, change the statement
bundle.putString("location", "19122");
in Listing 3-7 to a statement such as
bundle.putString("location",
locationText.getText().toString().trim());
In the service, send the incoming message’s text to the Weather Underground server.
That is, change the statement
reply.putString("weather", "It’s dark at night.");
in Listing 3-6 to a statement such as
reply.putString("weather", actualWeatherString);
The Weather Underground server takes a city name or a U.S. postal code (a zip code) and returns a JSON document describing the weather at that location. (To keep the example simple, I use only U.S. postal codes in this section’s listings.)
Listing 3-8 contains an abridged version of a response from the Weather Underground server.
In the service, sift information from the Weather Underground server’s response.
This section’s code ferrets out the Fahrenheit temperature and the weather condition from the Weather Underground’s response. (I apologize in advance to non-U.S., non-Belize readers for the use of the Fahrenheit scale. Bad habits are difficult to break.)
Listing 3-8: JSON Response from the Weather Underground
{
"response": {
"version":"0.1",
"features": {
"conditions": 1
}
}
,
"current_observation": {
"weather":"Overcast",
"temperature_string":"62.4 F (16.9 C)",
"temp_f":62.4,
"temp_c":16.9,
"relative_humidity":"88%",
"wind_dir":"SSE",
"wind_degrees":150,
"wind_mph":6.0,
}
}
You must have a Weather Underground account in order to run this section’s code. But don’t fret. For up to 500 queries per day, Weather Underground’s service is free. You can get an account by visiting www.wunderground.com/weather/api/
.
This section presents an alternative to the fake weather service in Listing 3-6. As you might expect, a real service is more complicated than a fake service. So it helps to digest this section’s code in pieces. The first piece is in Listing 3-9.
Listing 3-9: A Real Weather Service: Part I
package com.allyourcode.p03_03_09;
import android.app.Service;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class MyWeatherService extends Service {
Messenger messengerToClient = null;
MyIncomingHandler myIncomingHandler =
new MyIncomingHandler();
Messenger messengerToService =
new Messenger(myIncomingHandler);
@Override
public IBinder onBind(Intent intent) {
doToast(R.string.service_bound);
return messengerToService.getBinder();
}
class MyIncomingHandler extends Handler {
@Override
public void handleMessage(Message incomingMessage) {
messengerToClient = incomingMessage.replyTo;
new MyAsyncTask().execute
(incomingMessage.getData().getString("location"));
}
}
@Override
public boolean onUnbind(Intent intent) {
doToast(R.string.service_stopped_itself);
stopSelf();
return false;
}
@Override
public void onDestroy() {
myIncomingHandler = null;
doToast(R.string.service_destroyed);
}
void doToast(int resource) {
Toast.makeText(this, resource, Toast.LENGTH_SHORT).show();
}
// … More to come in Listings 3-10 and 3-11
Looking at Listing 3-9, you may have a distinct sense of déjà vu. In fact, Listing 3-9 is almost identical to Listing 3-6. The only difference is the body of the handleMessage
method. (Come to think of it, that’s not too surprising. Both listings need the plumbing for responding to binding requests and queries. The only difference is the way the two listings get their weather data.)
In Listing 3-6, the handleMessage
method composes a fake reply and sends the reply back to the client. But in Listing 3-9, handleMessage
delegates the work to a mysterious-looking thing called an AsyncTask
. Listing 3-10 contains your weather service’s AsyncTask
code.
Listing 3-10: A Real Weather Service: Part II
// … Continued from Listing 3-9
class MyAsyncTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String… locations) {
return getWeatherFromInternet(locations[0]);
}
@Override
protected void onPostExecute(String actualWeatherString) {
sendWeatherToClient(actualWeatherString);
doToast(R.string.message_handled);
}
}
// … More to come in Listing 3-11
The class in Listing 3-10 is an inner class of the MyWeatherService
class. I have a lot more to say about the AsyncTask
construct in Book IV, Chapter 3. In this chapter, I make only two points:
An AsyncTask
runs in a thread of its own.
That’s important because a service runs in the same thread as the client that invokes the service. Your service has to reach out along the Internet to Weather Underground’s servers. That takes time, time that the client’s busy user interface code might not have.
Imagine putting a request to Weather Underground right inside the handleMessage
method. The handleMessage
method (and everything else inside the service’s code) runs inside the same thread as the client activity. So the client activity, with its EditText
field and buttons, can do nothing while the handleMessage
method waits to get information back from Weather Underground. If the user presses a button, nothing happens. The button’s shade doesn’t even change to show that it’s been pressed. That’s bad.
Before Honeycomb, a developer was free to issue Internet requests inside an activity’s thread. It was bad programming practice, but Android would let you do it. Starting with Honeycomb, requests of this kind are strictly forbidden. A call for network data from the service’s regular code throws a NetworkOnMainThreadException
, and the app’s request is not fulfilled.
To fix this problem, Listing 3-10 executes an AsyncTask
. An AsyncTask
operates outside of its originator’s thread, so an AsyncTask
can make Internet requests on its originator’s behalf.
The execution of an AsyncTask
proceeds in distinct phases.
The code in Listing 3-10 has two methods — namely, doInBackground
and onPostExecute
.
In an AsyncTask
, the doInBackground
method is where all the heavy lifting occurs.
In Listing 3-10, the doInBackground
method reaches out to the Internet (via method calls) and actually waits for the response from Weather Underground. This step can be time-consuming, but because the AsyncTask
isn’t blocking the client activity’s responses, the wait is tolerable.
At the end of its execution, the doInBackground
method returns a value. In Listing 3-10, this value is a String
such as "31.7° F Overcast"
— a value obtained through a request sent over the Internet.
In an AsyncTask
, the onPostExecute
method starts running only after the doInBackground
method has finished whatever it has to do.
The onPostExecute
method’s parameter is whatever value the doInBackgroud
method returned. So, in Listing 3-10, the value of actualWeatherString
is something like "31.7° F Overcast"
. The job of the onPostExecute
method is to bundle this information into a reply and send that reply back to the client.
The rest of this section’s code makes good on the promises made inside Listing 3-10 — namely, to implement the getWeatherFromInternet
and sendWeatherToClient
methods.
Listing 3-11: A Real Weather Service: Part III
// … Continued from Listing 3-10
private static final String WEATHER_UNDERGROUND_URL =
"http://api.wunderground.com/" +
"api/your_key_id/conditions/q/";
private static final String CONDITION = "weather";
private static final String TEMP_F = "temp_f";
String getWeatherFromInternet(String location) {
String temperature = "", condition = "", weatherString;
URL url;
if (location != null && !location.equals("")) {
try {
url = new URL
(WEATHER_UNDERGROUND_URL + location + ".json");
HttpURLConnection connection =
(HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
BufferedReader reader = new BufferedReader
(new InputStreamReader(input));
String oneLineFromInternet;
String wholeReplyFromInternet = "";
while ((oneLineFromInternet = reader.readLine())
!= null) {
wholeReplyFromInternet +=
oneLineFromInternet + " ";
}
JSONObject jsonObject =
new JSONObject(wholeReplyFromInternet);
JSONObject current_observation =
jsonObject.getJSONObject("current_observation");
temperature = current_observation.getString(TEMP_F);
condition = current_observation.getString(CONDITION);
} catch (JSONException | IOException e) {
e.printStackTrace();
}
weatherString = temperature
+ (char) 0x00B0 + "F " + condition;
} else {
weatherString = "It’s dark at night.";
}
return weatherString;
}
void sendWeatherToClient(String actualWeatherString) {
Bundle reply = new Bundle();
reply.putString("weather", actualWeatherString);
Message replyMessage = Message.obtain();
replyMessage.setData(reply);
try {
messengerToClient.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
Listing 3-11 contains a lot of code, but most of it either comes straight from Listing 3-6 or is boilerplate Java code (not Android-specific code).
The stuff about HttpURLConnection
, InputStream
, and BufferedReader
is the way Java sends a URL to an Internet server.
The while
loop in Listing 3-11 gets the server’s response, line by line, and appends this response to its own wholeReplyFromInternet
string. When all is said and done, that string looks something like the JSON code in Listing 3-8. (Of course, in real life, that response string is quite a bit longer.)
The business about JSONObject
is Java’s way of sifting values from a JSON string.
Notice the nesting in Listing 3-8. The whole listing has two parts: a "response"
part and a "current_observation"
part. And the "current_observation"
part has several parts of its own, two of which are the "weather"
and "temp_f"
parts.
(As far as I’m concerned, the name "weather"
is an unfortunate choice. The whole thing is about weather. The pair "weather":"Overcast"
in Listing 3-8 is only a small piece of the overall weather picture. But "weather":"Overcast"
is what the Weather Underground server deals to me, so "weather":"Overcast"
is what I must use. In Listing 3-11, I create an alias CONDITION
for this strange use of the name "weather"
.)
In Listing 3-11, I extract the "weather"
and "temp_f"
values from the code in Listing 3-8. Because of the nesting in Listing 3-8, I perform the extraction in three steps:
Using the JSONObject
constructor, I get something that I can analyze with Java’s JSON methods.
I put this thing in the jsonObject
variable.
I call the jsonObject
variable’s getJSONObject
method to sift the "current_observation"
part from the server’s response.
I put this "current_observation"
part into the observation
variable.
I call the observation
variable’s getString
method to sift the "temp_f"
and "weather"
values out of the "current_observation"
part.
I put these values into the temperature
and condition
variables.
As a final coup de grâce, I combine the temperature
and condition
strings into one weatherString
. I return that weatherString
so that, down the line in the code, the weatherString
can be returned to the client code and displayed on the user’s screen.
In Listing 3-11, the only other excitement comes from the (char) 0x00B0
value. The hex value B0
(decimal value 176) is the Unicode representation for the degree symbol. (See the text view in Figure 3-4.)
In your Android-related travels, you might not always get JSON back from an Internet server. Another popular format for data interchange is XML. Because Weather Underground offers both JSON and XML, I can illustrate the use of XML by translating the previous section’s example.
To get weather data from Weather Underground, you send a URL. If the URL ends with .json
, you get a response like the stuff in Listing 3-8. But if the URL ends with .xml
, you get something like the response in Listing 3-12.
Listing 3-12: XML Response from the Weather Underground
<response>
<version>0.1</version>
<features>
<feature>conditions</feature>
</features>
<current_observation>
<weather>Mostly Cloudy</weather>
<temperature_string>65.7 F (18.7 C)</temperature_string>
<temp_f>65.7</temp_f>
<temp_c>18.7</temp_c>
<relative_humidity>71%</relative_humidity>
<wind_dir>South</wind_dir>
<wind_degrees>177</wind_degrees>
<wind_mph>15.2</wind_mph>
</current_observation>
</response>
There are two popular ways to get information from an XML document — DOM and SAX:
DOM (Document Object Model) picks apart an entire XML document, loads all this information into memory, and then lets you query the parser for values anywhere in the document. “What are the characters inside the <temp_f>
element in the <current_conditions>
element?” you ask. The DOM parser answers, but only after analyzing the entire document.
DOM works much like the code in Listing 3-11, but tends to consume lots and lots of memory.
SAX (Simple API for XML) scans an XML document one piece at a time, keeping only the current piece in memory. At every step, a SAX parser offers to report its findings. “I found a start tag” or “I found some characters between start and end tags,” says the parser. This section’s SAX code monitors parser findings for relevant data and adds any useful data to an instance of the Weather
class.
SAX isn’t as memory-intensive as DOM.
The SAX code to monitor the response from Weather Underground is in Listing 3-13.
Listing 3-13: Your Very Own SAX Handler
package com.allyourcode.p03_03_13;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class MySaxHandler extends DefaultHandler {
private static final String CONDITION = "weather";
private static final String TEMP_F = "temp_f";
private Weather weather = new Weather();
private boolean isInTemp_f = false;
private boolean isInCondition = false;
public Weather getWeather() {
return weather;
}
@Override
public void startElement(String namespaceURI,
String localName,
String qName,
Attributes attributes)
throws SAXException {
if (localName.equals(TEMP_F)) {
isInTemp_f = true;
} else if (localName.equals(CONDITION)) {
isInCondition = true;
}
}
@Override
public void characters(char[] ch,
int start,
int length) {
if (isInTemp_f) {
weather.temperature =
new String(ch, start, length);
} else if (isInCondition) {
weather.condition = new String(ch, start, length);
}
}
@Override
public void endElement(String namespaceURI,
String localName,
String qName)
throws SAXException {
if (localName.equals(TEMP_F)) {
isInTemp_f = false;
} else if (localName.equals(CONDITION)) {
isInCondition = false;
}
}
}
The SAX handler makes use of a tiny Weather
class shown in Listing 3-14.
Listing 3-14: (Not much to see, here!)
package com.allyourcode.p03_03_13;
public class Weather {
String temperature = "0.0";
String condition = "";
}
The code in Listing 3-13 has more to do with XML than with Android, so I don’t go into detail about the code in Listing 3-13.
Listing 3-15 contains the XML/SAX alternative to the getWeatherFromInternet
method in Listing 3-11.
Listing 3-15: Getting Weather the XML/SAX Way
String getWeatherFromInternet(String location) {
Weather weather = null;
String weatherString;
URL url;
if (location != null && !location.equals("")) {
try {
url = new URL
(WEATHER_UNDERGROUND_URL + location + ".xml");
SAXParser parser =
SAXParserFactory.newInstance().newSAXParser();
XMLReader reader = parser.getXMLReader();
MySaxHandler saxHandler = new MySaxHandler();
reader.setContentHandler(saxHandler);
reader.parse(new InputSource(url.openStream()));
weather = saxHandler.getWeather();
} catch (SAXException |
ParserConfigurationException |
IOException e) {
e.printStackTrace();
}
weatherString = weather.temperature
+ (char) 0x00B0 + "F " + weather.condition;
} else {
weatherString = "It’s dark at night.";
}
return weatherString;
}
The method in Listing 3-15 does what Java programs do when they get a response from a web server and submit the response to a SAX parser. I review the steps briefly because the code is mostly boilerplate. You can paste it into your own app with barely any changes.
SAXParser
instance and then use the parser to get an XMLReader
instance (whatever an XMLReader
instance is).MySaxHandler
instance (refer to Listing 3-10) and feed the MySaxHandler
instance to the XMLReader
instance.newInputSource(url.openStream())
.parse
method, feeding Weather Underground’s response to the reader.Weather
instance from the SAX handler.Whew!
Where I come from, you’re thought to be sophisticated if you’re multilingual. Do you speak a foreign language? If so, you’re cool.
Learning a second language is easy. Just find something whose acronym ends with the letter L, and learn how to use it. In this section, you read about AIDL — the Android Interface Definition Language.
Aside from being a language, AIDL is a programming idiom. AIDL is a way of rewriting some of your Java code to make it more natural and more straightforward.
In Listings 3-6 and 3-7, a service and its client pass messages back and forth. The message-passing paradigm is nice, but wouldn’t life be simpler if the client could simply call one of the service’s methods? That’s exactly what AIDL does for your code.
Here’s how AIDL works:
Create two projects.
One project has the service; the other project has the client. The service project has no activity; the client project starts with a blank activity.
In this section’s listings, the service’s package name is com.allyourcode.aidlservice
, and the client’s package name is com.allyourcode.aidlclient
.
app
branch.In the context menu that appears, select New ⇒ AIDL ⇒ AIDL File.
A dialog box appears. The dialog box has an Interface Name field. In this set of instructions, I accept the default name IMyAidlInterface
.
Click Finish.
As a result, the Project tool window has a new branch — an app/aidl
branch containing an IMyAidlInterface.aidl
item. In the editor, you see the text inside the new IMyAidlInterface.aidl
file.
Modify the IMyAidlInterface.aidl
file’s text, as shown in Listing 3-16.
The .aidl
file describes the kind of information to be passed between the service and the client. The file is almost a plain old Java source file. The big difference is the use of the non-Java keyword in
. This in
keyword tells the world that the service’s code receives a String
value (instead of sending out a String
value).
In the service project’s main menu, choose Build ⇒ Make Project.
This forces Android Studio to create some Java code. The new Java code implements some of the things that are described in your AIDL file.
Find the Java code that was generated in Step 6.
To do so, switch the Project tool window from the Android view to the Project view. Then expand the app/build/generated/source/aidl/debug/com.allyourcode.aidlservice
branch. Inside this branch, you see an IMyAidlInterface
branch.
The IMyAidlInterface.java
file is long and complicated. You can look at the IMyAidlInterface.java
file by double-clicking the Project view’s IMyAidlInterface
branch, but you’ll be just as happy if you don’t.
Both the service and the client must have copies of the AIDL information. So your next step is to put a copy of the IMyAidlInterface
stuff in the client project.
Right-click (or on a Mac, Ctrl-click) the app/java
branch. In the resulting context menu, select New ⇒ Package.
A Choose Destination Directory dialog box appears. Most likely, the dialog box wants you to choose between main
and androidTest
.
In the dialog box, select main
, and then click OK.
A New Package dialog box appears.
In the dialog box’s Enter New Package Name field, type the name of the service app’s package. Then click OK.
If you’re following these instructions to the letter, the service app’s package name is com.allyourcode.aidlservice
.
Now, in addition to its aidlclient
branch, the client’s Project tool window contains an aidlservice
branch. The aidlservice
branch is a sub-branch of the app/java/com.allyourcode
branch.
Right-click (or on a Mac, Ctrl-click) the aidlservice
branch. Then, in the resulting context menu, choose New ⇒ AIDL ⇒ AIDL File.
As in Step 3, a dialog box appears. For the Interface Name, type the same name that you typed in Step 3.
Click Finish.
As in Step 4, the Project tool window has a new app/aidl branch containing an IMyAidlInterface.aidl
item. In the editor, you see the text inside the new IMyAidlInterface.aidl
file.
Modify the IMyAidlInterface.aidl
file’s text, as shown in Listing 3-16.
Now both the service and the client have IMyAidlInterface.aidl
files, and both of these files belong to the com.allyourcode.aidlservice
package.
In the client project’s main menu, choose Build ⇒ Make Project.
As a result, both the service and the client have IMyAidlInterface.java
files.
Now it’s time to add some regular Java code (along with layout files and strings.xml
files) to your two projects. The Java code in this section is very much like the service and client classes in Listings 3-6 and 3-7. But in this section’s code, you don’t worry about messengers and binders. The automatically generated IMyAidlInterface.java
code takes care of that messy stuff on your behalf.
In the service app’s Project tool window, right-click the com.allyourcode.service
branch.
As usual, Ctrl-click if you’re using a Mac.
In the resulting context menu, select New ⇒ Service ⇒ Service.
A dialog box appears. The dialog box has a Class Name field and two check boxes. The default class name is MyService
. The Exported and Enabled check boxes are checked.
Click Finish to accept the defaults.
When the dust settles, you see a new MyService
class in Android Studio’s editor.
Modify the MyService
class’s code, as shown in the upcoming Listing 3-17.
The code in Listing 3-17 is quite a bit simpler than the code in Listing 3-6. The WeatherFetcher
class in Listing 3-17 takes the place of the messengers and the handler in Listing 3-6.
In the client project, modify the ActivityMain
class’s code, as shown momentarily in Listing 3-18.
Notice the call to reporter.fetchWeather
in Listing 3-18. Back in Listing 3-7, the client code passes a message to the service. But with the code in Listing 3-18, the client uses an ordinary method call to get data from the service. That’s much simpler than message passing.
Add layout and strings.xml
files to the client project.
You can reuse the files you used for Listing 3-7.
Run the client app.
The client communicates with the service through the AIDL mechanisms. The weather report appears on your screen, and the experiment is a success!
Listing 3-16: An AIDL file
package com.allyourcode.aidlservice;
interface IMyAidlInterface {
String fetchWeather(in String location);
}
The code in Listing 3-6 has a messenger and an incoming message handler. The message handler sends a message to be delivered to the client. In contrast, the code in Listing 3-12 has a fetchWeather
method, which simply returns a String
value. The class in Listing 3-12 can’t shoot the messenger because the class doesn’t even see the messenger.
In Listings 3-17 and 3-18, the developer is free of the messy messaging business when one process communicates with another. So the developer — that’s you! — can concentrate instead on the underlying application logic. Nice stuff!
Listing 3-17: The Service Code Using AIDL
package com.allyourcode.aidlservice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MyService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new WeatherFetcher();
}
class WeatherFetcher extends IMyAidlInterface.Stub {
public String fetchWeather(String city) {
String weatherString = null;
if (city != null) {
weatherString = "It’s dark at night.";
}
return weatherString;
}
}
@Override
public boolean onUnbind(Intent intent) {
stopSelf();
return false;
}
}
Listing 3-18: The Client Code Using AIDL
package com.allyourcode.aidlclient;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.allyourcode.aidlservice.IMyAidlInterface;
public class MainActivity extends Activity {
IMyAidlInterface reporter;
ServiceConnection connection = new MyServiceConnection();
SharedPreferences prefs;
boolean isBound = false;
void bind() {
Intent intent = new Intent();
intent.setClassName("com.allyourcode.aidlservice",
"com.allyourcode.aidlservice.MyService");
isBound = bindService(intent, connection,
Context.BIND_AUTO_CREATE);
}
public void queryService() {
if (isBound) {
try {
String report = reporter
.fetchWeather(locationText.getText()
.toString());
textView.setText(report);
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
textView.setText(R.string.service_not_bound);
}
}
void unbind() {
if (isBound) {
unbindService(connection);
isBound = false;
}
}
class MyServiceConnection implements ServiceConnection {
public void onServiceConnected(
ComponentName className, IBinder binder) {
reporter = IMyAidlInterface.Stub
.asInterface(binder);
}
public void onServiceDisconnected(ComponentName n) {
}
}
// For rest of this listing, copy the code from
// Listing 3-7 (from the onDestroy method,
// downward).
44.220.184.63