As noted previously, Android services are for long-running processes that may need to keep running even when decoupled from any activity. Examples include playing music even if the player activity gets garbage-collected, polling the Internet for RSS/Atom feed updates, and maintaining an online chat connection even if the chat client loses focus due to an incoming phone call.
Services are created when manually started (via an API call) or when some activity tries connecting to the service via interprocess communication (IPC). Services will live until no longer needed and if RAM needs to be reclaimed, or until shut down (on their own volition or because no one is using them anymore). Running for a long time isn't without its costs, though, so services need to be careful not to use too much CPU or keep radios active too much of the time, lest the service cause the device's battery to get used up too quickly.
This chapter covers how you can create your own services. The next chapter covers how you can use such services from your activities or other contexts. Both chapters will analyze the Service/WeatherPlus
sample application. This chapter focuses mostly on the WeatherPlusService
implementation. WeatherPlusService
extends the weather-fetching logic of the original Internet/Weather
sample, by bundling it in a service that monitors changes in location, so the weather is updated as the emulator is “moved.”
Creating a service implementation shares many characteristics with building an activity. You inherit from an Android-supplied base class, override some life-cycle methods, and hook the service into the system via the manifest.
So, the first step in creating a service is to extend the Service
class—in our case, with our own WeatherPlusService
subclass.
Just as activities have onCreate()
, onResume()
, onPause()
, and the like, Service
implementations have their own life-cycle methods, such as the following:
onCreate()
: As with activities, called when the service process is created, by any means.onStart()
: Called each time the service is started via startService()
.onDestroy()
: Called as the service is being shut down.For example, here is the onCreate()
method for WeatherPlusService
:
@Override
public void onCreate() {
super.onCreate();
client=new DefaultHttpClient();
format=getString(R.string.url);
mgr=(LocationManager)getSystemService(Context.LOCATION_SERVICE);
mgr.requestLocationUpdates(LocationManager.GPS_PROVIDER,
10000, 10000.0f, onLocationChange);
}
First, we chain upward to the superclass, so Android can do any setup work it needs to have done. Then we initialize our HttpClient component and format string as we did in the Weather
demo. We then get the LocationManager
instance for our application and request to get updates as our location changes, via the GPS LocationProvider
, which will be discussed in greater detail in Chapter 32.
The onDestroy()
method is much simpler:
@Override
public void onDestroy() {
super.onDestroy();
mgr.removeUpdates(onLocationChange);
}
Here, we just shut down the location-monitoring logic, in addition to chaining upward to the superclass for any Android internal bookkeeping that might be needed.
In addition to those life-cycle methods, your service also needs to implement onBind()
. This method returns an IBinder
, which is the linchpin behind the IPC mechanism. We will examine onBind()
a bit more closely in the next section.
Services, by default, run in the same process as all other components of the application, such as its activities. Hence, you can call API methods on the service object—if you can get your hands on it. Ideally, there would be some means, perhaps even type-safe, to ask Android to give you the local service object. Unfortunately, at the time of this writing, there is no such API. Hence, we are forced to cheat.
Any given service can, at most, have one copy running in memory. There might be zero copies in memory, if the service has not been started. But even if multiple activities try using the service, only one will actually be running. This is a fine implementation of the singleton pattern—all we need to do is expose the singleton itself, so other components can access the object.
We could expose the singleton via a public static data member or a public static getter method. However, then we run into some memory-management risks. Since everything referenced from a static context is immune to garbage collection, we would need to be very careful to set the static reference to null
in our service's onDestroy()
. Otherwise, our service, while disconnected from Android, would remain in memory indefinitely, until Android elected to shut down our process.
Fortunately, there is an alternative, and that is using onBind()
.
Binding allows a service to expose an API to activities (or other services) that bind to it. Much of this infrastructure is set up to support remote services, where the bound-to API is available via IPC, so one service can expose its API to other applications. However, the simple act of binding itself can be useful in situations where the service and its clients are all in the same application—the local service scenario.
To expose the service itself to activities via local binding, you must first create a public inner class that extends the android.os.Binder
class:
public class LocalBinder extends Binder {
WeatherPlusService getService() {
return(WeatherPlusService.this);
}
}
Here, our binder
exposes one method: getService()
, which returns the service itself. In a remote service scenario, this would not work, because the limitations of IPC prevent us from passing services between processes. However, for local services, this is a perfectly fine binder.
Next, we need to return that binder
object in our onBind()
method:
@Override
public IBinder onBind(Intent intent) {
return(binder);
}
At this point, any client that binds to our service will be able to access the service object itself and call methods on it. We will go into this in greater detail in the next chapter.
Finally, you need to add the service to your AndroidManifest.xml
file, for it to be recognized as an available service for use. That is simply a matter of adding a service
element as a child of the application
element, providing android:name
to reference your service class.
For example, here is the AndroidManifest.xml
file for WeatherPlus
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.service">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application android:label="@string/app_name"
android:icon="@drawable/cw">
<activity android:name=".WeatherPlus" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".WeatherPlusService" />
</application>
</manifest>
Since the service class is in the same Java namespace as everything else in this application, we can use the shorthand dot notation (".WeatherPlusService"
) to reference our class.
If you want to require some permission of those who wish to start or bind to the service, add an android:permission
attribute naming the permission you are mandating. See Chapter 28 for more details.
Sometimes, the service needs to asynchronously alert an activity of some occurrence.
For example, the theory behind the WeatherPlusService
implementation is that the service gets “tickled” when the device (or emulator) position changes. At that point, the service calls out to the web service and generates a new forecast web page for the activity to display. Then the service needs to let the activity know that a new forecast is available, so the activity can load and display it.
To interoperate with components this way, there are two major alternatives: callbacks and broadcast Intent
s.
Note that if all your service needs to do is alert the user of some event, you may wish to consider using a notification (described in Chapter 31), as that is the more normal way to handle that requirement.
Since an activity can work with a local service directly, an activity could provide some sort of listener object to the service, which the service could then call when needed. To make this work, you would need to:
The biggest catch is to make sure that the activity retracts the listeners when it is done. Listener objects generally know their activity, explicitly (via a data member) or implicitly (by being implemented as an inner class). If the service is holding onto defunct listener objects, the corresponding activities will linger in memory, even if the activities are no longer being used by Android. This represents a big memory leak. You may wish to use WeakReference
s, SoftReference
s, or similar constructs to ensure that if an activity is destroyed, any listeners it registers with your service will not keep that activity in memory.
An alternative approach, first mentioned in Chapter 17, is to have the service send a broadcast Intent
that can be picked up by the activity—assuming the activity is still around and is not paused. We will look at the client side of this exchange in Chapter 30. Here, let's examine how the service can send a broadcast.
The high-level implementation of the flow is packaged in FetchForecastTask
, an AsyncTask
implementation that allows us to move the Internet access to a background thread:
class FetchForecastTask extends AsyncTask<Location, Void, Void> {
@Override
protected Void doInBackground(Location... locs) {
Location loc=locs[0];
String url=String.format(format, loc.getLatitude(),
loc.getLongitude());
HttpGet getMethod=new HttpGet(url);
try {
ResponseHandler<String> responseHandler=new BasicResponseHandler();
String responseBody=client.execute(getMethod, responseHandler);
String page=generatePage(buildForecasts(responseBody));
synchronized(this) {
forecast=page;
}
sendBroadcast(broadcast);
}
catch (Throwable t) {
android.util.Log.e("WeatherPlus",
"Exception in updateForecast()", t);
}
return(null);
}
@Override
protected void onProgressUpdate(Void... unused) {
// not needed here
}
@Override
protected void onPostExecute(Void unused) {
// not needed here
}
}
Much of this is similar to the equivalent piece of the original Weather
demo. It performs the HTTP request, converts that into a set of Forecast
objects, and turn those into a web page. The first difference, besides the introduction of the AsyncTask
, is that the web page is simply cached in the service, since the service cannot put the page directly into the activity's WebView
. The second difference is that we call sendBroadcast()
, which takes an Intent
and sends it out to all interested parties. That Intent
is declared up front in the class prologue:
private Intent broadcast=new Intent(BROADCAST_ACTION);
Here, BROADCAST_ACTION
is simply a static String
with a value that will distinguish this Intent
from all others:
public static final String BROADCAST_ACTION=
"com.commonsware.android.service.ForecastUpdateEvent";
In Android, services can either be local or remote. Local services run in the same process as the launching activity. Remote services run in their own process. A detailed discussion of remote services can be found in The Busy Coder's Guide to Advanced Android Development (CommonsWare, 2009).
We will return to this service in Chapter 32, at which point we will flesh out how locations are tracked (and, in this case, mocked up).
18.222.32.67