Building a background weather service

Một phần của tài liệu Manning android in action 3rd (Trang 145 - 149)

In a basic Android application, you create Activity classes and move from screen to screen using Intent calls, as we’ve done in previous chapters. This approach works for the canonical Android screen-to-screen fore- ground application, but it doesn’t work for cases like ours where we want to always listen for changes in the weather, even if the user doesn’t currently have our app open. For this, we need a Service.

In this section, we’ll implement the Weather- AlertService we launched in listing 4.4. This Service sends an alert to the user when it learns of severe weather in a specified location. This alert will display over any application, in the form of a Notification, if severe weather is detected. Figure 4.5 shows the notifi- cation we’ll send.

A background task is typically a process that doesn’t involve direct user interaction or any type of UI. This

Listing 4.5 WeatherAlertServiceReceiver BroadcastReceiver class

Figure 4.5 Warning from a background application about severe weather

process perfectly describes checking for severe weather. After a Service is started, it runs until it’s explicitly stopped or the system kills it. The WeatherAlertService back- ground task, which starts when the device boots via the BroadcastReceiver from list- ing 4.5, is shown in the following listing.

public class WeatherAlertService extends Service { private static final String LOC = "LOC";

private static final String ZIP = "ZIP";

private static final long ALERT_QUIET_PERIOD = 10000;

private static final long ALERT_POLL_INTERVAL = 15000;

public static String deviceLocationZIP = "94102";

private Timer timer;

private DBHelper dbHelper;

private NotificationManager nm;

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

List<Location> locations = dbHelper.getAllAlertEnabled();

for (Location loc : locations) {

WeatherRecord record = loadRecord(loc.zip);

if (record.isSevere()) { if ((loc.lastalert +

WeatherAlertService.ALERT_QUIET_PERIOD) < System.currentTimeMillis()) {

loc.lastalert = System.currentTimeMillis();

dbHelper.update(loc);

sendNotification(loc.zip, record);

} } }

. . . device location alert 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));

} };

@Override

public void onCreate() {

dbHelper = new DBHelper(this);

timer = new Timer();

timer.schedule(task, 5000,

WeatherAlertService.ALERT_POLL_INTERVAL);

nm = (NotificationManager)

getSystemService(Context.NOTIFICATION_SERVICE);

}

. . . onStart with LocationManager and LocationListener \ omitted for brevity

@Override

Listing 4.6 WeatherAlertService class, used to register locations and send alerts

Get locations with alerts enabled

B

Fire alert if severe

C

Notify UI from handler

D

Initialize timer

E

public void onDestroy() { super.onDestroy();

dbHelper.cleanup();

}

@Override

public IBinder onBind(Intent intent) { return null;

}

protected WeatherRecord loadRecord(String zip) { final YWeatherFetcher ywh = new YWeatherFetcher(zip, true);

return ywh.getWeather();

}

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

handler.sendMessage(message);

}

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

nm.notify(Integer.parseInt(zip), n);

} }

WeatherAlertService extends Service. We create a Service in a way that’s similar to how we’ve created activities and broadcast receivers: extend the base class, implement the abstract methods, and override the lifecycle methods as needed.

After the initial class declaration, we define several member variables. First come constants that describe our intervals for polling for severe weather and a quiet period.

We’ve set a low threshold for polling during development—severe weather alerts will spam the emulator often because of this setting. In production, we’d limit this to check every few hours.

Next, our TimerTask variable will let us periodically poll the weather. Each time the task runs, it gets all the user’s saved locations through a database call B. We’ll examine the specifics of using an Android database in chapter 5.

Clean up

database connection

F

Display actionable notification

G

When 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 sending a NotificationC. After we process the user’s saved locations, we get the device’s alert location from the database using a postal code designation. If the user has requested alerts for their current location, we repeat the process of polling and sending an alert for the device’s current location as well. You can see more details on Android location-related facilities in chapter 11.

After defining our TimerTask, we create a Handler member variable. This variable will receive a Message object that’s fired from a non-UI thread. In this case, after receiving the Message, our Handler calls a helper method that instantiates and dis- plays a NotificationD.

Next, we override the Service lifecycle methods, starting with onCreate(). Here comes the meat of our Service: a TimerE that we configure to repeatedly fire. For as long as the Service continues to run, the timer will allow us to update weather information. After onCreate(), you see onDestroy(), where we clean up our database connection F. Service classes provide these lifecycle methods so you can control how resources are allocated and deallocated, similar to Activity classes.

After the lifecycle-related methods, we implement the required onBind() method.

This method returns an IBinder, which other components that call into Service methods will use for communication. WeatherAlertService performs only a back- ground task; it doesn’t support binding, and so it returns a null for onBind. We’ll add binding and interprocess communication (IPC) in section 4.5.

Next, we implement our helper methods. First, loadRecord() calls out to the Yahoo! Weather API via YWeatherFetcher. (We’ll cover networking tasks, similar to those this class performs, in chapter 6.) Then sendNotification configures a Message with location details to activate the Handler we declared earlier. Last of all, you see the notifyFromHandler() method. This method fires off a Notification with Intent objects that will call back into the WeatherReporter Activity if the user clicks the NotificationG.

A warning about long-running Services

Our sample application starts a Service and leaves it running in the background.

This Service is designed to have a minimal footprint, but Android best practices dis- courage long-running Services. Services that run continually and constantly use the network or perform CPU-intensive tasks will eat up the device’s battery life and might slow down other operations. Even worse, because they run in the background, users won’t know what applications are to blame for their device’s poor performance.

The OS will eventually kill running Services if it needs to acquire additional memory, but otherwise it won’t interfere with poorly designed Services. If your use case no longer requires the Service, you should stop it. If you do require a long-running Service, you might want to give users the option of whether to use it.

Now that we’ve discussed the purpose of Services and you’ve created a Service class and started one via a BroadcastReceiver, we can start looking at how other developers can interact with your Service.

Một phần của tài liệu Manning android in action 3rd (Trang 145 - 149)

Tải bản đầy đủ (PDF)

(662 trang)