在这一章,你将修改在第5章创建的地震监视器的例子(在第6、7章增加了一些功能)。在这个例子中,你将将地震更新和处理的功能移到一个独立的Service组件中。

 

在本章的后面,你将为这个Service添加额外的功能,将网络查询和XML解析放到后台线程中。再之后,你将使用Toast和Notification来警告用户有新的地震信息。

 

1. 创建一个扩展了Service类的EarthquakeService类。

 

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;
}
}

 

2. 将这个Service添加到manifest中,通过在application节点中添加一个新的service标签来完成。

 

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

3. 将refreshEarthquakes和addNewQuake方法从Earthquake Activity中移到EarthquakeService中。

 

你还需要移除对addQuakeToArray和loadQuakesFromProvider的调用(仍然把这两方法留在Earthquake Activity中,是因为它们在Activity中仍然需要)。在EarthquakeService中还需要移除所有对earthquakes的引用。

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) 
{
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;
g = (Element)entry.getElementsByTagName(“georss:point”).item(0);
Element when;
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(“dummyGPS”);
l.setLatitude(Double.parseDouble(location[0]));
l.setLongitude(Double.parseDouble(location[1]));
String magnitudeString = details.split(“ “)[1];
int end = magnitudeString.length()-1;
double magnitude;
magnitude = Double.parseDouble(magnitudeString.substring(0, end));
details = details.split(“,”)[1].trim();
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 {
}
}

 

4. 在Earthquake Activity中,创建一个新的refreshEarthquakes方法。它应该显式地启动EarthquakeService。

private void refreshEarthquakes() {
startService(new Intent(this, EarthquakeService.class));
}

 

5. 返回到EarthquakeService中。重写onStart和onCreate方法来支持一个新的定时器,用于更新地震列表。使用第6章创建的SharedPreference对象来决定地震数据是否要正常更新。

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”);
}

6. EarthquakeService现在会按照更新时间来更新Earthquake Provider。这个信息现在还没有传回到Earthquake Activity中的ListView和EarthquakeMap Activity中。

 

为了通知其它的组件和其它对地震数据感兴趣的应用程序,修改EarthquakeService,在一个新的地震数据添加时广播一个Intent。

 

6.1. 修改addNewQuake方法来调用新的announceNewQuake方法。

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. 在annouceNewQuake方法中,当新的地震数据找到时广播一个Intent。

 

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. 以上完成EarthquakeService的实现。现在,你需要修改两个Activity组件来监听Service广播的Intent,并且进行相应的更新显示。

 

7.1. 在Earthquake Activity中,创建一个内部的EarthquakeReceiver类,扩展BroadcastReceiver。重写onReceive方法来调用loadQuakesFromProvider方法,更新数组和更新列表。

 

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

7.2. 重写onResume方法来注册新的Receiver并且当Activity变成活跃状态时更新ListView。重写onPause方法,当Activity从前台移出时反注册Receiver。

 

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. 在EarthquakeMap Activity中做相同的事情,这次,在收到Intent无效MapView之前调用Cursor的requery方法。

 

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();
}
}

 

现在,当Earthquake Activity启动后,它将启动EarthquakeService。这个Service将在后台运行、更新Earthquake的Content Provider,甚至在Activity中止或关闭之后。

 

在本章你将继续更新和增强EarthquakeService,第一次使用Toast和Notification。

 

在这个时刻,地震的处理已经在Service中运行了,但它们仍然在主线程中执行。在本章的后面,你将学习如何将耗时的操作移到后台线程来改善性能以及避免“应用程序无响应”的消息。