7.1: Location services
Contents:
- Introduction
- Setting up Google Play services
- Requesting location permissions
- Requesting last known location
- Geocoding and reverse geocoding
- Creating a LocationRequest object
- Working with the user's location settings
- Requesting location updates
- Related practical
- Learn more
Using location in an app can greatly improve the user experience by providing contextual information. For example, you may want your app to be aware that the user is at the gym to start tracking their fitness, or to turn on Wi-Fi when they go to a friend's house.
The system obtains the device's location using a combination of GPS, Wi-Fi, and cell network technologies. Google Play services provides the FusedLocationProviderClient
class, a convenient way to let the system make location requests on your behalf. A fused location service estimates device location by combining, or "fusing," all the available location providers, including GPS location and network location. It does this to balance fast, accurate results with minimal battery drain.
The fused location provider methods return a Location
object, which contains geographic coordinates in the form of latitude and longitude. If your app requires a physical address, you can convert from latitude/longitude coordinates (and back) using the Geocoder
class.
The user controls location settings on their device, including settings for location precision. The user can also turn off location services completely. If your app requires location services, you can detect the current location settings and prompt the user to change them in a system dialog.
Setting up Google Play services
Before you can use the features provided by Google Play services, you must install the Google Repository, which includes the Google Play services SDK. To install the Google Repository and update the Android SDK Manager:
- Open Android Studio.
- Select Tools > Android > SDK Manager.
- Select the SDK Tools tab.
- Expand Support Repository, select Google Repository, and click OK.
To add Google Play services to your project, add the following line of code to the dependencies
section in your app-level build.gradle (Module: app)
file:
compile 'com.google.android.gms:play-services:XX.X.X'
Replace XX.X.X
with the latest version number, for example 11.0.2
. Android Studio will let you know if you need to update it. For more information and the latest version number, see Add Google Play Services to Your Project.
Now that you have Google Play services installed, you're ready to connect to the LocationServices
API.
Requesting location permissions
Apps that use location services must request location permissions. Android offers two location permissions that you can include in the manifest:
ACCESS_COARSE_LOCATION
: The API returns a location with an accuracy approximately equivalent to a city block.ACCESS_FINE_LOCATION
: The API returns the most precise location data available.
Starting with Android 6.0 (API level 23), you also have to request "dangerous" permissions at runtime. The user can revoke permissions at any time, so each time you use location services, check for permissions. For more information, see Requesting Permissions at Runtime.
Requesting the last known location
Once your app connects to the LocationServices
API, here's how to get the last known location of a user's device:
- Create an instance of a
FusedLocationProviderClient
using theLocationServices.getFusedLocationProviderClient()
method. - Call the
FusedLocationProviderClient
getLastLocation()
method to retrieve the device location. The precision of the location returned by this call is determined by the permission setting you put in your app manifest, as described above.
The getLastLocation()
method returns a Task
object. A Task
object represents an asynchronous operation (usually an operation to fetch some value, in this case a Location
object). You retrieve the latitude and longitude of a location from the Location
object. In rare cases when the location is not available, the Location
object is null
.
The Task
class supplies methods for adding success and failure listeners. If the operation is successful, the success listener delivers the desired object. If the operation is unsuccessful, the failure listener delivers an Exception
:
mFusedLocationClient.getLastLocation().addOnSuccessListener(
new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
if (location != null) {
mLastLocation = location;
}
}
});
mFusedLocationClient.getLastLocation().addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.e(TAG, "onFailure: ", e.printStackTrace());
}
}
);
Note that the getLastLocation()
method doesn't actually force the fused location provider to obtain a new location. Instead, it retrieves the most recently obtained location from a local cache. On a physical device, there is usually a service that fetches and caches the location right after the device is restarted.
An emulator, however, does not have such a service, so getLastLocation()
is more likely to return null
. To force the fused location provider to obtain a new location, start the Google Maps app and accept the terms and conditions (if you haven't already). Use the Location tab of the emulator settings to deliver a location to the fused location provider. This forces a value to be cached, and getLastLocation()
no longer returns null
.
Geocoding and reverse geocoding
- Geocoding is the process of converting a street address into a set of coordinates (like latitude and longitude).
- Reverse geocoding is the process of converting a set of coordinates into a human-readable address.
The location services API methods provide information about the device's location using Location
objects. A Location
object contains a latitude, longitude, timestamp, and optional parameters such as bearing, speed, and altitude. Although latitude and longitude are useful for calculating distance or displaying a map position, in many cases the address of the location is more useful. For example, if you want to let your users know where they are or what is close by, a street address is more meaningful than the latitude and longitude.
You can use the Geocoder
class to do both geocoding and reverse geocoding. The amount of detail in a reverse-geocoded location description varies; for example it might contain the full street address of the closest building, or only a city name and postal code.
The Geocoder
class supplies the following methods:
getFromLocation(double latitude, double longitude, int maxResults)
: Use this method for reverse geocoding. ThegetFromLocation()
method takes coordinates as arguments and returns a list ofAddress
objects.List<Address> addresses = geocoder.getFromLocation( location.getLatitude(), location.getLongitude(), 1);
getFromLocationName(String locationName, int maxResults)
: Use this method to geocode a location name into a set of coordinates. Like thegetFromLocation()
method, thegetFromLocationName()
method returns a list ofAddress
objects that contain latitude and longitude coordinates. It can also be used with additional parameter to specify a rectangle to search within.
BothList<Address> addresses = geocoder .getFromLocationName("731 Market St, San Francisco, CA 94103", 1) Address firstAddress = addresses.get(0); double latitude = firstAddress.getLatitude(); double longitude = firstAddress.getLongitude();
getFromLocation()
andgetFromLocationName()
perform a network lookup and therefore may take a while to complete. For this reason you should not call them on the main thread.
Geocoder
class requires a backend service that is not included in the core Android framework. The Geocoder
query methods return an empty list if there is no backend service in the platform. To determine whether a Geocoder
implementation exists, use the isPresent()
method.
Creating a LocationRequest object
If your app requires precise tracking that involves periodic updates, you need to create a LocationRequest
object that contains the requirements of the location updates. The fused location provider attempts to batch location requests from different apps together, in order to preserve battery.
The options you can set for a LocationRequest
include:
setInterval()
: This method sets how frequently your app needs location updates, in milliseconds. If another app is receiving updates more frequently, location updates may be more frequent than this interval. Updates may also be less frequent than this interval, or there may be no updates at all, for example if the device has no connectivity.setFastestInterval()
: This method sets a limit, in milliseconds, to the update rate. The location APIs send out updates at the fastest rate that any app has requested withsetInterval()
. If this rate is faster than your app can handle, you may encounter UI flicker or data overflow. To prevent these problems, callsetFastestInterval()
.setPriority()
: This method sets the priority of the request, which gives the location APIs a strong hint about which location sources to use. The following values are supported:PRIORITY_BALANCED_POWER_ACCURACY
: Use this setting to request location precision to within a city block, which is an accuracy of approximately 100 meters. This is considered a coarse level of accuracy, and is likely to consume less power. With this setting, the location services are likely to use Wi-Fi and cell-tower positioning to determine location. Note, however, that the choice of location provider depends on many other factors, such as which sources are available.PRIORITY_HIGH_ACCURACY
: Use this setting to request the most precise location possible. With this setting, the location services are more likely to use GPS to determine location.PRIORITY_LOW_POWER
: Use this setting to request city-level precision, which is an accuracy of approximately 10 kilometers. This is considered a coarse level of accuracy, and is likely to consume less power.PRIORITY_NO_POWER
: Use this setting if you need zero additional power consumption, but want to receive location updates when available. With this setting, your app does not trigger any location updates, but receives locations triggered by other apps.
For a list of other options you can set, see LocationRequest
.
private LocationRequest getLocationRequest() {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(10000);
locationRequest.setFastestInterval(5000);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
return locationRequest;
}
Working with the user's location settings
Activating the GPS and network capabilities of a device uses significant power. Additionally, users may have privacy concerns about their location being tracked.This is why users have a device setting that lets them control the balance between accuracy and power for location requests. Users can also turn off location services completely.
The user's settings may reduce the accuracy of location tracking to the point where it reduces your app's usability. For example, if the user turns off location, it doesn't make sense to have a tracker app running and using system resources. You can detect the device settings and prompt the user to change them to match what you've set in your LocationRequest
.
To check the device settings:
- Create a
LocationSettingsRequest
and add one or more location requests.LocationSettingsRequest settingsRequest = new LocationSettingsRequest.Builder() .addLocationRequest(mLocationRequest).build();
- Create a
SettingsClient
object using theLocationServices.getSettingsClient()
method, and pass in the context:SettingsClient client = LocationServices.getSettingsClient(this);
Use the
SettingsClient
checkLocationSettings()
method to see if the device settings match theLocationRequest
. Pass in theLocationSettingsRequest
that you created in step 1:Task<LocationSettingsResponse> task = client .checkLocationSettings(settingsRequest);
The
checkLocationSettings()
method returns aTask
object, just like thegetLastLocation()
method discussed above.Use the
OnFailureListener
to catch the case where the device settings do not match theLocationRequest
.The
Exception
passed into the resultingonFailure()
method contains aStatus
object with information about whether the device settings match theLocationRequest
.If the device settings don't match the
LocationRequest
, the status code isLocationSettingsStatusCodes.RESOLUTION_REQUIRED
. In this case, show the user a dialog that prompts them to change their settings, then handle the user's decision to either change their settings or back out:Call the
startResolutionForResult()
method on the status.task.addOnFailureListener(this, new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { int statusCode = ((ApiException) e).getStatusCode(); if (statusCode == LocationSettingsStatusCodes .RESOLUTION_REQUIRED) { // Location settings are not satisfied, but this can // be fixed by showing the user a dialog try { // Show the dialog by calling // startResolutionForResult(), and check the // result in onActivityResult() ResolvableApiException resolvable = (ResolvableApiException) e; resolvable.startResolutionForResult (MainActivity.this, REQUEST_CHECK_SETTINGS); } catch (IntentSender.SendIntentException sendEx) { // Ignore the error } } } });
Override the
onActivityResult()
callback in yourActivity
to handle the user's decision (either to update their settings or to back out). Make sure therequestCode
matches the constant that you used in thestartResolutionForResult()
method.@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CHECK_SETTINGS) { if (resultCode == RESULT_CANCELED) { stopTrackingLocation(); } else if (resultCode == RESULT_OK) { startTrackingLocation(); } } super.onActivityResult(requestCode, resultCode, data); }
Requesting location updates
After checking the device settings and prompting the user to update them if they don't match your location request, you can start requesting periodic location updates using the LocationRequest
and the FusedLocationProvider
Client
objects. The accuracy of the location is determined by the available location providers (network and GPS), the location permissions you requested, and the options you set in the location request.
To request and start location updates:
- Create a
LocationRequest
object with the desired parameters for your location updates, as described in Creating a LocationRequest object above. - The fused location provider invokes the
LocationCallback.onLocationResult()
callback method. Create an instance ofLocationCallback
, and override itsonLocationResult()
method:mLocationCallback = new LocationCallback() { @Override public void onLocationResult(LocationResult locationResult) { for (Location location : locationResult.getLocations()) { // Update UI with location data // ... } }; }
- To start the regular updates, call
requestLocationUpdates()
on theFusedLocationProviderClient
. Pass in theLocationRequest
andLocationCallback
. This starts the location updates, which are delivered to theonLocationResult()
method.
Stopping location updates
When you no longer need location updates, stop them using the FusedLocationProviderClient
removeLocationUpdates()
method, passing it your instance of the LocationCallback
.
You might want to stop location updates when the activity is no longer in focus, for example when the user switches to another app or to a different activity in the same app. Stopping location updates at these times can reduce power consumption, provided the app doesn't need to collect information even when it's running in the background.
Related practical
The related practical documentation is in 7.1 P: Using the device location.
Learn more
Android developer documentation: