Any real estate agent will tell you the three most important things are location, location, location. Android understands that and lets your application know where the device is. (It won't tell you if the device is in a good school district, although I am pretty sure there is an application for that.) Listing 7–8 shows how to request location updates using the system location services.
private void requestLocationUpdates() {
LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
List<String> providers = lm.getAllProviders();
if (providers != null&& ! providers.isEmpty()) {
LocationListener listener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.i(TAG, location.toString());
}
@Override
public void onProviderDisabled(String provider) {
Log.i(TAG, provider + " location provider disabled");
}
@Override
public void onProviderEnabled(String provider) {
Log.i(TAG, provider + " location provider enabled");
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras)
{
Log.i(TAG, provider + " location provider status changed to " + status);
}
};
for (String name : providers) {
Log.i(TAG, "Requesting location updates on " + name);
lm.requestLocationUpdates(name, 0, 0, listener);
}
}
}
NOTE: Your application needs either the ACCESS_COARSE_LOCATION
permission or the ACCESS_FINE_LOCATION
permission to be able to retrieve the location information. The GPS location provider requires the ACCESS_FINE_LOCATION
permission while the network provider requires either ACCESS_COARSE_LOCATION
or ACCESS_FINE_LOCATION
.
This piece of code has serious flaws, but you should still run it, for example in your application's onCreate()
method, in order to see the impact this code has on the battery life.
On a Galaxy Tab 10.1, one can observe the following:
If you let your application with this piece of code run for a while, you should see the battery level going down faster than usual. This code's three main flaws are:
Luckily, all these flaws can easily be fixed. Ultimately though, how you need to use the location services depends on what your application needs to do so there is no one single solution for all applications, so what is considered a flaw in one application may be a feature in another. Also note that there is typically not one single solution for all users: you should consider the needs of your users as well and offer different settings in your application to cater for your end-users. For example, some users may be willing to sacrifice battery life to get more frequent location updates while others would rather limit the number of updates to make sure their device does not need to be charged during their lunch break.
In order to unregister a listener, you can simply call removeUpdates()
as showed in Listing 7–9. It is usually good to do that in onPause()
like for broadcast receivers but your application many need to do that elsewhere. Listening for location updates for a long period of time will consume a lot of power so your application should try to get the information it needs and then stop listening for updates. In some cases it can be a good idea to offer a way for the user to force a location fix.
private void disableLocationListener(LocationListener listener) {
LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
lm.removeUpdates(listener);
}
The frequency of the updates can be adjusted when calling requestLocationUpdates()
.
Even though the LocationManager
class defines five requestLocationUpdates()
methods, all have two parameters in common: minTime
and minDistance
. The first one, minTime
, specifies the time interval for notifications in milliseconds. This is used only as a hint for the system to save power and the actual time between updates may be greater (or less) than specified. Obviously, greater values will result in greater power savings. The second one, minDistance
, specifies the minimum distance interval for notifications. Greater values can still result in power savings as fewer instructions would be executed if your application is not notified of all location updates. Listing 7–10 shows how to register for updates with one hour between updates and a 100-meter threshold.
private void requestLocationUpdates() {
LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
List<String> providers = lm.getAllProviders();
if (providers != null) {
for (String name : providers) {
LocationListener listener = new LocationListener() {
…
};
Log.i(TAG, "Requesting location updates on " + name);
lm.requestLocationUpdates(name, DateUtils.HOUR_IN_MILLIS * 1, 100,
listener);
}
}
}
Choosing the right interval is more art than science and once again depends on your application. A navigation application would typically require frequent updates, but if the same navigation application can be used by hikers, for example, updates could be less frequent. Moreover, you may have to trade accuracy for power in some cases. Give your users the choice and offer settings that can be adjusted to let them choose the best behavior for themselves.
As mentioned earlier, Android defines multiple location providers:
The GPS location provider (LocationManager.GPS_PROVIDER
) is typically the most accurate one, with a horizontal accuracy of about 10 meters (11 yards). The network location provider is not as accurate as the GPS, and the accuracy will depend on how many locations the system can use to compute the device's location. For example, my own logs show an accuracy of 48 meters for locations coming from the network location provider, or the width of a football field.
While more accurate, GPS locations are expensive in terms of time and battery usage. Getting a “fix” from the GPS location provider requires locking the signals from multiple satellites, which can take from a few seconds in an open field to an infinite amount of time if the device is indoors and signal cannot be locked (like in a parking garage when your car's GPS tries to acquire satellites). For example, it took about 35 seconds to get the first location update after GPS was enabled while inside the house, but only 5 seconds when the same test was repeated outside. When using the network location provider, it took 5 seconds to get the first location update, inside and outside. An Assisted GPS (AGPS) would typically provide faster locations fixes, but actual times would depend on information already cached by the device and the network access.
NOTE: The Galaxy Tab 10.1 used for these measurements is Wi-Fi only. Faster location fixes would be achieved by the network provider if cell ids were also used.
Even though receiving updates from multiple providers was listed as a flow earlier, it may not always be one. As a matter of fact, you may want to receive updates from several providers for a while in order to get a more accurate location fix.
The passive location provider is the one that can preserve the battery the most. When your application uses the passive location provider, it says it is interested in location updates but won't be proactively requesting location fixes. In other words, your application will simply wait for other applications, services, or system components to request location updates and will be notified together with these other listeners. Listing 7–11 shows how to receive passive location updates. To test whether your application receives updates, open another application that makes use of the location services, such as Maps.
private void requestPassiveLocationUpdates() {
LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
LocationListener listener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.i(TAG, "[PASSIVE] " + location.toString());
// let's say you only care about GPS location updates
if (LocationManager.GPS_PROVIDER.equals(location.getProvider())) {
// if you care about accuracy, make sure you call hasAccuracy()
// (same comment for altitude and bearing)
if (location.hasAccuracy() && (location.getAccuracy() < 10.0f)) {
// do something here
}
}
}
@Override
public void onProviderDisabled(String provider) {
Log.i(TAG, "[PASSIVE] " + provider + " location provider disabled");
}
@Override
public void onProviderEnabled(String provider) {
Log.i(TAG, "[PASSIVE] " + provider + " location provider enabled");
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.i(TAG, "[PASSIVE] " + provider + " location provider status changed
to " + status);
}
};
Log.i(TAG, "Requesting passive location updates");
lm.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
DateUtils.SECOND_IN_MILLIS * 30, 100, listener);
}
If you use that code and disable or enable Wi-Fi or GPS, you will notice the passive listener does not get notified when a provider is disabled or enabled, or when a provider's status changes. While this is usually not important, this may force your application to use the other location providers should it really care about being notified of these changes.
A good trade-off is to register a listener with the network location provider, which uses less power than the GPS provider, while also registering a listener with the passive location provider in order to get possibly more accurate location information from the GPS.
NOTE: Your application will need the ACCESS_FINE_LOCATION
to use the passive location provider even if it only receives location updates from the network provider. This may end up being a problem if you believe this may raise privacy concerns with your users. There is currently no way to receive passive updates only from the network location provider and only ask for the ACCESS_COARSE_LOCATION
permission.
Since our focus is on battery life, your application may want to filter out location providers with a high power requirement if using a passive location provider is not an option. Listing 7–12 shows how you can get the power requirement of all the location providers.
private static String powerRequirementCodeToString(int powerRequirement) {
switch (powerRequirement) {
case Criteria.POWER_LOW: return "Low";
case Criteria.POWER_MEDIUM: return "Medium";
case Criteria.POWER_HIGH: return "High";
default: return String.format("Unknown (%d)", powerRequirement);
}
}
private void showLocationProvidersPowerRequirement() {
LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
List<String> providers = lm.getAllProviders();
if (providers != null) {
for (String name : providers) {
LocationProvider provider = lm.getProvider(name);
if (provider != null) {
int powerRequirement = provider.getPowerRequirement();
Log.i(TAG, name + " location provider power requirement: " +
powerRequirementCodeToString(powerRequirement));
}
}
}
}
However, since your application may have very specific needs, it may be easier to first specify as precisely as possible what location provider you are looking for. For example, your application may want to use a location provider that reports speed information in addition to coordinates. Listing 7–13 shows how you can create a Criteria object and find out which location provider you should be using.
private LocationProvider getMyLocationProvider() {
LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
LocationProvider provider = null;
// define your criteria here
criteria.setAccuracy(Criteria.ACCURACY_COARSE);
criteria.setAltitudeRequired(true);
criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT); // API level 9
criteria.setBearingRequired(false);
criteria.setCostAllowed(true); // most likely you want the user to be able to
set that
criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW); // API level 9
criteria.setPowerRequirement(Criteria.POWER_LOW);
criteria.setSpeedAccuracy(Criteria.ACCURACY_MEDIUM); // API level 9
criteria.setSpeedRequired(false);
criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT); // API level 9
List<String> names = lm.getProviders(criteria, false); // perfect matches only
if ((names != null) && ! names.isEmpty()) {
for (String name : names) {
provider = lm.getProvider(name);
Log.d(TAG, "[getMyLocationProvider] " + provider.getName() + " "+
provider);
}
provider = lm.getProvider(names.get(0));
} else {
Log.d(TAG, "Could not find perfect match for location provider");
String name = lm.getBestProvider(criteria, false); // not necessarily
perfect match
if (name != null) {
provider = lm.getProvider(name);
Log.d(TAG, "[getMyLocationProvider] " + provider.getName() + " " +
provider);
}
}
return provider;
}
LocationManager.getProviders()
and LocationManager.getBestProvider()
differ quite significantly. While getProviders()
will only return perfect matches, getBestProvider()
will first search for a perfect match but may return a location provider that does not meet the criteria if no provider was a perfect match. The criteria are loosened in the following sequence:
Since this order may not necessarily be the policy you want to adopt to find a location provider, you may have to develop your own algorithm to find the right provider for your needs. Also, the algorithm may depend on the current battery status: your application may not be willing to loosen the power requirement criterion if the battery is low.
Before you decide to register a location listener with a location provider, you may first want to check if a location is already known (and was cached by the system). The LocationManager
class defines the getLastKnownLocation()
method, which returns the last known location for a given provider, or null if no known location exists. While this location may be out of date, it is often a good starting point since this location can be retrieved instantly and calling this method won’t start the provider. Even applications that register a location listener usually first retrieve the last known location in order to be more responsive as it typically takes a few seconds before any location update is received. Listing 7–14 shows how a last known location can be retrieved.
private Location getLastKnownLocation() {
LocationManager lm = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
List<String> names = lm.getAllProviders();
Location location = null;
if (names != null) {
for (String name : names) {
if (! LocationManager.PASSIVE_PROVIDER.equals(name)) {
Location l = lm.getLastKnownLocation(name);
if ((l != null) && (location == null || l.getTime() >
location.getTime())) {
location = l;
/*
* Warning: GPS and network providers' clocks may be out of sync
so comparing the times
* may not be such a good idea... We may not get the most recent
location fix after all.
*/
}
}
}
}
return location;
}
While a GPS is sensitive to satellite signals, most Android devices come with other types of sensors that can make Android applications more interesting than what you would typically find on a traditional computer.
18.227.13.219