An Earthquake Monitoring Service Example

In this chapter you'll modify the Earthquake example you started in Chapter 5 (and continued to enhance in Chapters 6, 7, and 8). In this example you'll move the earthquake updating and processing functionality into a separate Service component.

Prepared for ASHLEE KABAT, email: [email protected] Order number: 56760408 This PDF is for the purchaser's personal use in accordance with the Wrox Terms of Service and under US copyright as stated on this book's copyright page. If you did not purchase this copy, please visit www.wrox.com to purchase your own copy.

Later in this chapter you'll build additional functionality within this Service, starting by moving the network lookup and XML parsing to a background thread. Later you'll use Toasts and Notifications to alert users of new earthquakes.

1. Start by creating a new EarthquakeService that extends Service. package com.paad.earthquake;

import android.app.Service; import android.content.Intent; import android.os.IBinder; import java.util.Timer; import java.util.TimerTask;

public class EarthquakeService extends Service { ©Override public void onCreate() {

// TODO: Initialize variables, get references to GUI objects

©Override public IBinder onBind(Intent intent) { return null;

2. Add this new Service to the manifest by adding a new service tag within the application node.

<service android:enabled="true" android:name=".EarthquakeService"/>

3. Move the refreshEarthquakes and addNewQuake methods out of the Earthquake Activity and into the EarthquakeService.

You'll need to remove the calls toiddQuakeToArray and loadQuakesFromProvider (leave both of these methods in the Earthquake Activity because they're still required). In the EarthquakeService also remove all references to the earthquakes ArrayList.

private void addNewQuake(Quake _quake) { ContentResolver cr = getContentResolver();

// Construct a where clause to make sure we don't already have // this earthquake in the provider. String w = EarthquakeProvider.KEY_DATE + " = " + _quake.getDate().getTime();

// If the earthquake is new, insert it into the provider. Cursor c = cr.query(EarthquakeProvider.CONTENT_URI, null, w, null, null); if (c.getCount()==0){

ContentValues values = new ContentValues();

values.put(EarthquakeProvider.KEY_DATE, _quake.getDate().getTime());

values.put(EarthquakeProvider.KEY_DETAILS, _quake.getDetails());

double lat = _quake.getLocation().getLatitude(); double lng = _quake.getLocation().getLongitude(); values.put(EarthquakeProvider.KEY_LOCATION_LAT, lat); values.put(EarthquakeProvider.KEY_LOCATION_LNG, lng); values.put(EarthquakeProvider.KEY_LINK, _quake.getLink()); values.put(EarthquakeProvider.KEY_MAGNITUDE, _quake.getMagnitude());

cr.insert(EarthquakeProvider.CONTENT_URI, values);

private void refreshEarthquakes() { // Get the XML URL url; try {

String quakeFeed = getString(R.string.quake_feed); url = new URL(quakeFeed);

URLConnection connection; connection = url.openConnection();

HttpURLConnection httpConnection =

(HttpURLConnection)connection; int responseCode = httpConnection.getResponseCode();

if (responseCode == HttpURLConnection.HTTP_OK) { InputStream in = httpConnection.getInputStream();

DocumentBuilderFactory dbf =

DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder();

// Parse the earthquake feed.

Document dom = db.parse(in);

Element docEle = dom.getDocumentElement();

// Get a list of each earthquake entry.

NodeList nl = docEle.getElementsByTagName("entry");

for (int i = 0 ; i < nl.getLength(); i++) { Element entry = (Element)nl.item(i); Element title; title =

(Element)entry.getElementsByTagName("title").item(0); Element g =

(Element)entry.getElementsByTagName("georss:point").item(0); Element when =

(Element)entry.getElementsByTagName("updated").item(0); Element link =

(Element)entry.getElementsByTagName("link").item(0);

String details = title.getFirstChild().getNodeValue();

String hostname = "http://earthquake.usgs.gov";

String linkString = hostname + link.getAttribute("href");

String point = g.getFirstChild().getNodeValue(); String dt = when.getFirstChild().getNodeValue(); SimpleDateFormat sdf;

sdf = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'"); Date qdate = new GregorianCalendar(0,0,0).getTime(); try {

qdate = sdf.parse(dt); } catch (ParseException e) { e.printStackTrace();

String[] location = point.split(" "); Location l = new Location("parsed"); l.setLatitude(Double.parseDouble(location[0])); l.setLongitude(Double.parseDouble(location[1]));

String magnitudeString = details.split(" ")[1]; int end = magnitudeString.length()-1; double magnitude =

Double.parseDouble(magnitudeString.substring(0, end));

Quake quake = new Quake(qdate, details, l, magnitude, linkString);

// Process a newly found earthquake addNewQuake(quake);

} catch (MalformedURLException e) {

e.printStackTrace(); } catch (IOException e) {

e.printStackTrace(); } catch (ParserConfigurationException e) {

e.printStackTrace(); } catch (SAXException e) { e.printStackTrace();

4. Within the Earthquake Activity, create a new refreshEarthquakes method. It should explicitly start the EarthquakeService.

private void refreshEarthquakes() {

startService(new Intent(this, EarthquakeService.class));

5. Return to the EarthquakeService. Override the onStartCommand and onCreate methods to support a new Timer that will be used to update the earthquake list. onStartCommand should return start_sticky because we are using a timer to trigger multiple refreshes. This is generally poor form; the Timer behavior should be moved to a background thread and triggered by Alarms. You'll learn how to do both of these things later in this chapter.

Use the SharedPreference object created in Chapter 6 to determine if the earthquakes should be regularly updated.

private Timer updateTimer; private float minimumMagnitude;

@Override public int onStartCommand(Intent intent, int flags, int startId) { // Retrieve the shared preferences SharedPreferences prefs =

getSharedPreferences(Preferences.USER_PREFERENCE, Activity.MODE_PRIVATE);

int minMagIndex = prefs.getInt(Preferences.PREF_MIN_MAG, 0); if (minMagIndex < 0) minMagIndex = 0;

int freqIndex = prefs.getInt(Preferences.PREF_UPDATE_FREQ, 0); if (freqIndex < 0) freqIndex = 0;

boolean autoUpdate =

prefs.getBoolean(Preferences.PREF_AUTO_UPDATE, false);

Resources r = getResources();

int[] minMagValues = r.getIntArray(R.array.magnitude); int[] freqValues = r.getIntArray(R.array.update_freq_values);

minimumMagnitude = minMagValues[minMagIndex]; int updateFreq = freqValues[freqIndex];

updateTimer.cancel(); if (autoUpdate) {

updateTimer = new Timer("earthquakeUpdates"); updateTimer.scheduleAtFixedRate(doRefresh, 0, updateFreq*60*1000);

else refreshEarthquakes(); return Service.START_STICKY;

private TimerTask doRefresh = new TimerTask() { public void run() {

refreshEarthquakes();

@Override public void onCreate() {

updateTimer = new Timer("earthquakeUpdates");

6. The EarthquakeService will now update the earthquake Provider each time it is asked to refresh, as well as on an automated schedule (if one is specified). The updates are not yet passed back to the Earthquake Activity's List View or the Earthquake Map Activity.

To alert those components, and any other applications interested in earthquake data, modify the EarthquakeService to broadcast a new Intent whenever a new earthquake is added.

6.1. Modify the addNewQuake method to call a new announceNewQuake method.

public static final String NEW_EARTHQUAKE_FOUND = "New_Earthquake_Found";

private void addNewQuake(Quake _quake) { ContentResolver cr = getContentResolver();

// Construct a where clause to make sure we don't already have // this earthquake in the provider. String w = EarthquakeProvider.KEY_DATE +

// If the earthquake is new, insert it into the provider. Cursor c = cr.query(EarthquakeProvider.CONTENT_URI, null, w, null, null); if (c.getCount()==0){

ContentValues values = new ContentValues();

values.put(EarthquakeProvider.KEY_DATE, _quake.getDate().getTime()); values.put(EarthquakeProvider.KEY_DETAILS, _quake.getDetails());

double lat = _quake.getLocation().getLatitude(); double lng = _quake.getLocation().getLongitude(); values.put(EarthquakeProvider.KEY_LOCATION_LAT, lat); values.put(EarthquakeProvider.KEY_LOCATION_LNG, lng); values.put(EarthquakeProvider.KEY_LINK, _quake.getLink()); values.put(EarthquakeProvider.KEY_MAGNITUDE, _quake.getMagnitude());

cr.insert(EarthquakeProvider.CONTENT_URI, values); announceNewQuake(_quake);

private void announceNewQuake(Quake quake) { }

6.2. Within announceNewQuake, broadcast a new Intent whenever a new earthquake is found.

private void announceNewQuake(Quake quake) {

Intent intent = new Intent(NEW_EARTHQUAKE_FOUND); intent.putExtra("date", quake.getDate().getTime()); intent.putExtra("details", quake.getDetails());

intent.putExtra("longitude", quake.getLocation().getLongitude()); intent.putExtra("latitude", quake.getLocation().getLatitude()); intent.putExtra("magnitude", quake.getMagnitude());

sendBroadcast(intent);

7. That completes the EarthquakeService implementation. You still need to modify the two Activity components to listen for the Service Intent broadcasts and refresh their displays accordingly.

7.1. Within the Earthquake Activity, create a new internal EarthquakeReceiver class that extends BroadcastReceiver. Override the onReceive method to call loadFromProviders to update the earthquake array and refresh the list.

public class EarthquakeReceiver extends BroadcastReceiver { ©Override public void onReceive(Context context, Intent intent) { loadQuakesFromProvider();

7.2. Override the onResume method to register the new Receiver and update the List View contents when the Activity becomes active. Override onPause to unregister it when the Activity moves out of the foreground.

EarthquakeReceiver receiver;

©Override public void onResume() { IntentFilter filter;

filter = new IntentFilter(EarthquakeService.NEW_EARTHQUAKE_FOUND); receiver = new EarthquakeReceiver(); registerReceiver(receiver, filter);

loadQuakesFromProvider(); super.onResume();

©Override public void onPause() {

unregisterReceiver(receiver); super.onPause();

7.3. Do the same for the EarthquakeMap Activity, this time calling requery on the result Cursor before invalidating the Map View whenever the Intent is received.

EarthquakeReceiver receiver;

©Override public void onResume() {

earthquakeCursor.requery();

IntentFilter filter;

filter = new IntentFilter(EarthquakeService.NEW_EARTHQUAKE_FOUND); receiver = new EarthquakeReceiver(); registerReceiver(receiver, filter);

super.onResume();

©Override public void onPause() {

earthquakeCursor.deactivate();

super.onPause();

public class EarthquakeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { earthquakeCursor.requery();

MapView earthquakeMap = (MapView)findViewById(R.id.map_view); earthquakeMap.invalidate();

All code snippets in this example are part of the Chapter 9 Earthquake project, available for download at Wrox.com.

Now when the Earthquake Activity is launched it will start the Earthquake Service. This Service will then continue to run, updating the earthquake Content Provider in the background, even after the Activity is suspended or closed.

You'll continue to upgrade and enhance the Earthquake Service throughout the chapter, first using Toasts and later using Notifications and Alarms.

At this stage the earthquake processing is done in a Service, but it's still being executed on the main GUI thread. Later in this chapter you'll learn how tomove time-consuming operations onto background threads to improve performance and avoid ''Force Close'' messages.

Similarly, the Service is constantly running, taking up valuable resources. Later sections will explain how to replace the Timer with Alarms.

Mobile Apps Made Easy

Mobile Apps Made Easy

Quick start guide to skyrocket your offline and online business success with mobile apps. If you know anything about mobile devices, you’ve probably heard that famous phrase coined by one of the mobile device’s most prolific creators proclaiming that there’s an app for pretty much everything.

Get My Free Training Guide


Post a comment