Building a Service


Build your own Android App Dev Empire

Get Instant Access

In the typical Android application you create Activity classes and move from screen to screen using Intent calls. This is the approach we introduced in chapter 1 and used in other previous chapters. This works for the canonical Android screen-to-screen foreground application but is not applicable for a longer-running background process—for that you need a Service.

The Service we will work with here is the WeatherAlertService we sent an Intent request for in the WeatherAlertServiceReceiver in listing 4.4. This Service sends an alert to the user when there is severe weather in a location in which the user has indicated an interest. This alert will be displayed in any application, in the form of a Notification, by the background Service if severe weather is detected. The notifications we will send are shown in the screen shot in figure 4.5.

Figure 4.5

The Notification-based alert the WeatherAlertService displays to the user when severe weather is detected in the forecast

Figure 4.5

The Notification-based alert the WeatherAlertService displays to the user when severe weather is detected in the forecast

One key aspect of Android Service classes we need to cover prior to jumping in and implementing one is their dual-purpose nature. Something like the duality of man (you know, the "Jungian Thing"); services lead a double life.

4.3.1 Dual-purpose nature of a Service

In Android a Service is intended to serve two purposes: running a background task or exposing a remotable object for Inter-Process Communication (IPC). We will explore both of these purposes for a Service in turn. Although we are going to build separate Service instances for each purpose, you can also build one Service that serves both purposes, if needed.

A background task is typically a process that does not involve direct user interaction or any type of UI. This of course is a perfect fit for polling for severe weather. As far as exposing a remotable object for IPC, we will see how that works, and why it is necessary, in section 4.4.1. There we will build another Service that walks through creating and exposing a remotable object.

As we have already discussed briefly, and we will explain more about here as we go, a Service can either be started or bound or both. Starting a Service relates to the background task aspect. Once started, a Service runs until it is explicitly stopped (you will learn more about this in section 4.4, where we discuss the overall lifecycle of a Service). Binding to a Service involves using a ServiceConnection object to connect and get a remotable reference.

Creating the WeatherAlertService itself, which serves the first type of Service purpose and enables our background weather checks, is where we will focus next.

4.3.2 Creating a background task Service

The WeatherAlertService background task-focused Service, which is started when the device is booted via the BroadcastReceiver previously discussed, is shown in listing 4.6.

Listing 4.6 WeatherAlertService class, used to register locations and send alerts public class WeatherAlertService extends Service { <—© Extend Service private static final String LOC = "LOC";

private static final String ZIP = "ZIP"; Define private static final long ALERT_QUIET_PERIOD = 10000; |/

private static final long ALERT_POLL_INTERVAL = 15000; public static String deviceLocationZIP = "94102";

constants for polling intervals private Timer timer; private DBHelper dbHelper; private NotificationManager nm;

Get locations with ©

private TimerTask task = new TimerTask () { alerts enabled I

List<Location> locations = dbHelper. getAllAlertEnabled () ; <1—' for (Location loc : locations) {

WeatherRecord record = loadRecord(; if (record.isSevere()) { if ((loc.lastalert + WeatherAlertService . ALERT_QUIET_PERIOD) < System.currentTimeMillis() ) {

loc.lastalert = System.currentTimeMillis(); dbHelper.update(loc) ;, record); <1-1 Fire alert

. device location alert block omitted for brevity

private Handler handler = new Handler() { public void handleMessage(Message msg) {

notifyFromHandler((String) msg.getData ()

.get(WeatherAlertService.LOC) , (String) msg.getData()

.get(WeatherAlertService.ZIP)); <-1

} I Call notify method

}; F from handler

@Override public void onCreate () {

this . dbHelper = new DBHelper (this) ; <1—Q Set up database this.timer = new Timer();

this.timer.schedule(this.task, 5000,

WeatherAlertService . ALERT_POLL_INTERVAL); this.nm = (NotificationManager) H Set up notification getSystemService (Context .NOTIFICATION_SERVICE) ; <-1 manager

. onStart with LocationManager and LocationListener \ omitted for brevity

@Override public void onDestroy() { super.onDestroy(); this.dbHelper.cleanup();

I Clean up database ,,_I connection

@Override public IBinder onBind(Intent intent) { J Return nu|| return null; <-1 from onBind

} 1) Load a private WeatherRecord loadRecord (String zip) { I weather final YWeatherFetcher ywh = new YWeatherFetcher (zip, true) ; <)J record return ywh.getWeather(); } Include helper for handler 1!

private void notifyFromHandler (String location, String zip) { <]—'

Uri uri = Uri.parse("weather://com.msi.manning/loc?zip=" + zip); Intent intent = new Intent (Intent ,ACTION_VIEW, uri) ; PendingIntent pendingIntent =

PendingIntent .getActivity(this, Intent. FLAG_ACTIVITY_NEW_TASK, intent, PendingIntent. FLAG_ONE_SHOT); final Notification n =

new Notification(R.drawable.severe_weather_24, "Severe Weather Alert!", System.currentTimeMillis()); n.setLatestEventInfo(this, "Severe Weather Alert!", location, pendingIntent); this .nm. notify (Integer .parseInt (zip) , n) ; Include helper for

} notification private void sendNotification(String zip, WeatherRecord record) { < Message message = Message.obtain(); Bundle bundle = new Bundle();

bundle.putString(WeatherAlertService. ZIP, zip) ; bundle.putString(WeatherAlertService.LOC, record.getCity()

+ ", " + record.getRegion()); message.setData(bundle); this.handler.sendMessage(message);

The first thing of note in the WeatherAlertService class is the fact that it extends Service O. This is the same approach we have seen with activities and receivers: extend the base class, implement the abstract methods, and override the lifecycle methods as needed.

After the initial class declaration a series of member variables is defined. The first of these are constants that represent intervals for polling for severe weather and a quiet period Q. These are significant because we have set a very low threshold for polling during development—severe weather alerts will spam the emulator often because of this setting. In production this would be throttled back to once every 6 or 12 hours or such.

Next is a TimerTask variable that we will use to do the polling and get all of the user's saved locations that have alerting enabled, through a database call ©. We will learn the specifics of using a database in Android in the next chapter, where we will finish out the WeatherReporter application and focus on data; here we are going to stay on track with our Service discussion.

Once we have the saved locations, we parse each one and load the weather report. If the report shows severe weather in the forecast, we update the time of the last alert field and call a helper method to initiate a Notification being sent Q. After we process the user's saved locations, we get the device's alert location from the database using a special postal code designation. The process of polling and sending an alert is repeated for the device current location—as opposed to saved specific locations—if the user has this feature enabled. The device location itself is obtained via a LocationMan-ager. We have omitted the device location-related details here to stay focused, but complete details on Android location-related facilities are covered in chapter 11.

After our TimerTask is set up, we have a Handler member variable. This variable will be used later, using the same technique as in previous listings, to receive a Message object that is fired from a non-UI-related thread and then react. In this case, when the message is received, we call a helper method that instantiates and displays a Notification Q.

Beyond our member variables we come to the Service lifecycle methods that we have overridden, starting with onCreate. Inside this method we set up our database helper object G and a NotificationManager O- Again, we will cover data in the next chapter. (Alert and notification details are specifically addressed in chapter 8.) After onCreate we see onDestroy, which is where we clean up our database connection ©. Service classes have these lifecycle methods so we can control how resources are allocated and deallocated, similarly to Activity classes; in section 4.4.5 we will address this in more depth.

After the lifecycle-related methods we implement the required onBind method ©. This method returns an IBinder, which is generally what other components that call into Service methods use for communication. Service classes, as we discussed in section 4.3.1, can serve two purposes: first to run background processes and second for binding to enable IPC. Our weather alert Service is only performing a background task, not enabling IBinder/Binder-based IPC. Therefore, this class returns a null for onBind. We will delve into the binding and IPC aspect of a Service in section 4.4.

Next we see the implementations of our own helper type methods. First we have loadRecord, which is where we call out to the Yahoo! Weather API via YWeather-Fetcher 1). (How this works in terms of networking specifics will be covered in chapter 6.) Then we have sendNotification, which sets up a Message with location details to pass into our earlier declared Handler 1!. The way this method uses the handler ensures that processing time to get weather data doesn't hang the main UI thread. Lastly we see the notifyFromHandler method that is invoked from the Handler; this fires off a Notification with Intent objects that will call back into WeatherReporter if the user clicks on the Notification [email protected]

A warning about long-running services

We are starting a Service for our sample application here and then leaving it running in the background. Our service is designed to have a minimal footprint (when the polling is tuned), but in general long-running services are strongly discouraged. If your use case doesn't require it, you should make sure to stop any services you have started when your application exits. If you do require a long-running service, you may want to give the user the option of using it or not (a preference). Services are a bit of a paradox in this sense; they are for background tasks, but background is not intended to mean forever. For more discussion on this topic see the Android developers forum: thread/thread/fa2848e31636af70.

Now that we have discussed what services are for, have created a Service class, and have previously seen a service started via a BroadcastReceiver, we need to cover a bit more detail about the IPC process in Android and other Service details related to it, such as starting versus binding and lifecycle.

Was this article helpful?

0 0

Post a comment