|
Cyril Sermon (@admin) |
In this chapter, you’ll modify the Earthquake example you started in Chapter 5 (and continued to enhance in Chapters 6 and 7). In this example, you’ll move the earthquake updating and processing functionality into a separate Service component.
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.
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 onStart(Intent intent, int startId) {
// TODO: Actions to perform when service is started.
}
@Override
public void onCreate() {
// TODO: Initialize variables, get references to GUI objects
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
Add this new Service to the manifest by adding a new service tag within the application node.
<service android:enabled=”true” android:name=”.EarthquakeService”></service>
Move the refreshEarthquakes and addNewQuake methods out of the Earthquake Activity and into the EarthquakeService.
You’ll need to remove the calls to addQuakeToArray 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);
}
c.close();
}
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”); if (nl != null && nl.getLength() > 0) {
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();
}
finally {
}
}
Within the Earthquake Activity, create a new refreshEarthquakes method. It should explicitly start the EarthquakeService.
private void refreshEarthquakes() {
startService(new Intent(this, EarthquakeService.class));
}
Return to the EarthquakeService. Override the onStart and onCreate methods to support a new Timer that will be used to update the earthquake list. 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 void onStart(Intent intent, 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();
};
private TimerTask doRefresh = new TimerTask() { public void run() {
refreshEarthquakes();
}
};
@Override
public void onCreate() {
updateTimer = new Timer(“earthquakeUpdates”);
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). This information is not yet passed back to the Earthquake Activity’s ListView or the EathquakeMap 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 +
“ = “ + _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); announceNewQuake(_quake);
}
c.close();
}
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);
}
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 LiveView 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 MapView 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();
}
}
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 Notifications.
At this stage, the earthquake processing is done in a Service, but it’s still being executed on the main thread. Later in this chapter, you’ll learn how to move time-consuming operations onto background threads to improve performance and avoid “Application Unresponsive” messages.