Download presentation
Presentation is loading. Please wait.
1
Android 20: Location Kirk Scott
3
The Nerpa, or Baikal Seal, the Only Completely Freshwater Seal
4
How Did It Get There?
5
Introduction You may want to include location as part of your assignment or project
6
Outline 20.1 Making Your App Location-Aware
7
20.1 Making Your App Location-Aware
8
These classes teach you how to add user location and mapping information to your app.
Make your app more helpful and relevant to users by providing information about where they are and the world around them.
9
One of the unique features of mobile applications is location awareness.
Mobile users take their devices with them everywhere, and adding location awareness to your app offers users a more contextual experience. The location APIs available in Google Play services facilitate adding location awareness to your app with automated location tracking, geofencing, and activity recognition.
10
The Google Play services location APIs are preferred over the Android framework location APIs (android.location) as a way of adding location awareness to your app. If you are currently using the Android framework location APIs, you are strongly encouraged to switch to the Google Play services location APIs as soon as possible.
11
This class shows you how to use the Google Play services location APIs in your app to get the current location, get periodic location updates, and look up addresses. The class includes sample apps and code snippets that you can use as a starting point for adding location awareness to your app.
12
Note: Since this class is based on the Google Play services client library, make sure you install the latest version before using the sample apps or code snippets. To learn how to set up the client library with the latest version, see Setup in the Google Play services guide.
13
Getting the Last Known Location
Using the Google Play services location APIs, your app can request the last known location of the user's device. In most cases, you are interested in the user's current location, which is usually equivalent to the last known location of the device. Specifically, use the fused location provider to retrieve the device's last known location. The fused location provider is one of the location APIs in Google Play services.
14
It manages the underlying location technology and provides a simple API so that you can specify requirements at a high level, like high accuracy or low power. It also optimizes the device's use of battery power. This lesson shows you how to make a single request for the location of a device using the getLastLocation() method in the fused location provider.
15
Set Up Google Play Services
To access the fused location provider, your app's development project must include Google Play services. Download and install the Google Play services component via the SDK Manager and add the library to your project. For details, see the guide to Setting Up Google Play Services.
16
Specify App Permissions
Apps that use location services must request location permissions. Android offers two location permissions: ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION. The permission you choose determines the accuracy of the location returned by the API. If you specify ACCESS_COARSE_LOCATION, the API returns a location with an accuracy approximately equivalent to a city block.
17
This lesson requires only coarse location.
Request this permission with the uses-permission element in your app manifest, as the following code snippet shows: [See the following overhead]
18
<manifest xmlns:android="http://schemas. android
<manifest xmlns:android=" package="com.google.android.gms.location.sample.basiclocationsample" > <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> </manifest>
19
Connect to Google Play Services
To connect to the API, you need to create an instance of the Google Play services API client. For details about using the client, see the guide to Accessing Google APIs. In your activity's onCreate() method, create an instance of Google API Client, using the GoogleApiClient.Builder class to add the LocationServices API, as the following code snippet shows. [See the following overhead]
20
// Create an instance of GoogleAPIClient
// Create an instance of GoogleAPIClient. if (mGoogleApiClient == null) { mGoogleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(LocationServices.API) .build(); }
21
To connect, call connect() from the activity's onStart() method.
To disconnect, call disconnect() from the activity's onStop() method. The following snippet shows an example of how to use both of these methods. [See the following overhead]
22
protected void onStart() { mGoogleApiClient. connect(); super
protected void onStart() { mGoogleApiClient.connect(); super.onStart(); } protected void onStop() { mGoogleApiClient.disconnect(); super.onStop(); }
23
Get the Last Known Location
Once you have connected to Google Play services and the location services API, you can get the last known location of a user's device. When your app is connected to these you can use the fused location provider's 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 in the Specify App Permissions section of this document.
24
To request the last known location, call the getLastLocation() method, passing it your instance of the GoogleApiClient object. Do this in the onConnected() callback provided by Google API Client, which is called when the client is ready. The following code snippet illustrates the request and a simple handling of the response: [See the following overhead]
25
public class MainActivity extends ActionBarActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... public void onConnected(Bundle connectionHint) { mLastLocation = LocationServices.FusedLocationApi.getLastLocation( mGoogleApiClient); if (mLastLocation != null) { mLatitudeText.setText(String.valueOf(mLastLocation.getLatitude())); mLongitudeText.setText(String.valueOf(mLastLocation.getLongitude())); } } }
26
The getLastLocation() method returns a Location object from which you can retrieve the latitude and longitude coordinates of a geographic location. The location object returned may be null in rare cases when the location is not available. The next lesson, Changing Location Settings, shows you how to detect the current location settings, and prompt the user to change settings as appropriate for your app's requirements.
27
Changing Location Settings
If your app needs to request location or receive permission updates, the device needs to enable the appropriate system settings, such as GPS or Wi-Fi scanning. Rather than directly enabling services such as the device's GPS, your app specifies the required level of accuracy/power consumption and desired update interval, and the device automatically makes the appropriate changes to system settings.
28
These settings are defined by the LocationRequest data object.
This lesson shows you how to use the Settings API to check which settings are enabled, and present the Location Settings dialog for the user to update their settings with a single tap.
29
Connect to Location Services
In order to use the location services provided by Google Play Services and the fused location provider, connect your app using the Google API Client, then check the current location settings and prompt the user to enable the required settings if needed. For details on connecting with the Google API client, see Getting the Last Known Location.
30
Apps that use location services must request location permissions
Apps that use location services must request location permissions. For this lesson, coarse location detection is sufficient. Request this permission with the uses-permission element in your app manifest, as shown in the following example: [See the following overhead]
31
<manifest xmlns:android="http://schemas. android
<manifest xmlns:android=" package="com.google.android.gms.location.sample.locationupdates" > <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> </manifest>
32
If the device is running Android 6
If the device is running Android 6.0 or higher, and your app's target SDK is 23 or higher, the app has to list the permissions in the manifest and request those permissions at run time. For more information, see Requesting Permissions at Run Time.
33
Set Up a Location Request
To store parameters for requests to the fused location provider, create a LocationRequest. The parameters determine the level of accuracy for location requests. For details of all available location request options, see the LocationRequest class reference. This lesson sets the update interval, fastest update interval, and priority, as described below:
34
Update interval setInterval() - This method sets the rate in milliseconds at which your app prefers to receive location updates. Note that the location updates may be faster than this rate if another app is receiving updates at a faster rate, or slower than this rate, or there may be no updates at all (if the device has no connectivity, for example).
35
Fastest update interval
setFastestInterval() - This method sets the fastest rate in milliseconds at which your app can handle location updates. You need to set this rate because other apps also affect the rate at which updates are sent. The Google Play services location APIs send out updates at the fastest rate that any app has requested with setInterval(). If this rate is faster than your app can handle, you may encounter problems with UI flicker or data overflow. To prevent this, call setFastestInterval() to set an upper limit to the update rate.
36
Priority setPriority() - This method sets the priority of the request, which gives the Google Play services location services 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 WiFi and cell tower positioning. Note, however, that the choice of location provider depends on many other factors, such as which sources are available.
37
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 the 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 negligible impact on 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.
38
Create the location request and set the parameters as shown in this code sample:
protected void createLocationRequest() { LocationRequest mLocationRequest = new LocationRequest(); mLocationRequest.setInterval(10000); mLocationRequest.setFastestInterval(5000); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); }
39
The priority of PRIORITY_HIGH_ACCURACY, combined with the ACCESS_FINE_LOCATION permission setting that you've defined in the app manifest, and a fast update interval of 5000 milliseconds (5 seconds), causes the fused location provider to return location updates that are accurate to within a few feet. This approach is appropriate for mapping apps that display the location in real time.
40
Performance hint: If your app accesses the network or does other long-running work after receiving a location update, adjust the fastest interval to a slower value. This adjustment prevents your app from receiving updates it can't use. Once the long-running work is done, set the fastest interval back to a fast value.
41
Get Current Location Settings
Once you have connected to Google Play services and the location services API, you can get the current location settings of a user's device. To do this, create a LocationSettingsRequest.Builder, and add one or more location requests. The following code snippet shows how to add the location request that was created in the previous step: LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder() .addLocationRequest(mLocationRequest);
42
Next check whether the current location settings are satisfied:
PendingResult<LocationSettingsResult> result = LocationServices.SettingsApi.checkLocationSettings(mGoogleClient, builder.build());
43
When the PendingResult returns, your app can check the location settings by looking at the status code from the LocationSettingsResult object. To get even more details about the the current state of the relevant location settings, your app can call the LocationSettingsResult object's getLocationSettingsStates() method.
44
Prompt the User to Change Location Settings
To determine whether the location settings are appropriate for the location request, check the status code from the LocationSettingsResult object. A status code of RESOLUTION_REQUIRED indicates that the settings must be changed. To prompt the user for permission to modify the location settings, call startResolutionForResult(Activity, int).
45
This method brings up a dialog asking for the user's permission to modify location settings.
The following code snippet shows how to check the location settings, and how to call startResolutionForResult(Activity, int). [See the following overheads]
46
result.setResultCallback(new ResultCallback<LocationSettingsResult>()) { public void onResult(LocationSettingsResult result) { final Status status = result.getStatus(); final LocationSettingsStates = result.getLocationSettingsStates(); switch (status.getStatusCode()) { case LocationSettingsStatusCodes.SUCCESS: // All location settings are satisfied. The client can // initialize location requests here. ... break; case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
47
// 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(). status.startResolutionForResult( OuterClass.this, REQUEST_CHECK_SETTINGS); } catch (SendIntentException e) { // Ignore the error. } break; case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: // Location settings are not satisfied. However, we have no way // to fix the settings so we won't show the dialog. ... break; } } });
48
The next lesson, Receiving Location Updates, shows you how to receive periodic location updates.
49
Receiving Location Updates
If your app can continuously track location, it can deliver more relevant information to the user. For example, if your app helps the user find their way while walking or driving, or if your app tracks the location of assets, it needs to get the location of the device at regular intervals. As well as the geographical location (latitude and longitude), you may want to give the user further information such as the bearing (horizontal direction of travel), altitude, or velocity of the device.
50
This information, and more, is available in the Location object that your app can retrieve from the fused location provider. While you can get a device's location with getLastLocation(), as illustrated in the lesson on Getting the Last Known Location, a more direct approach is to request periodic updates from the fused location provider. In response, the API updates your app periodically with the best available location, based on the currently-available location providers such as WiFi and GPS (Global Positioning System).
51
The accuracy of the location is determined by the providers, the location permissions you've requested, and the options you set in the location request. This lesson shows you how to request regular updates about a device's location using the requestLocationUpdates() method in the fused location provider.
52
Get the Last Known Location
The last known location of the device provides a handy base from which to start, ensuring that the app has a known location before starting the periodic location updates. The lesson on Getting the Last Known Location shows you how to get the last known location by calling getLastLocation(). The snippets in the following sections assume that your app has already retrieved the last known location and stored it as a Location object in the global variable mCurrentLocation.
53
Apps that use location services must request location permissions.
In this lesson you require fine location detection, so that your app can get as precise a location as possible from the available location providers. Request this permission with the uses-permission element in your app manifest, as shown in the following example: [See the following overhead]
54
<manifest xmlns:android="http://schemas. android
<manifest xmlns:android=" package="com.google.android.gms.location.sample.locationupdates" > <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> </manifest>
55
Request Location Updates
Before requesting location updates, your app must connect to location services and make a location request. The lesson on Changing Location Settings shows you how to do this. Once a location request is in place you can start the regular updates by calling requestLocationUpdates(). Do this in the onConnected() callback provided by Google API Client, which is called when the client is ready.
56
Depending on the form of the request, the fused location provider either invokes the LocationListener.onLocationChanged() callback method and passes it a Location object, or issues a PendingIntent that contains the location in its extended data. The accuracy and frequency of the updates are affected by the location permissions you've requested and the options you set in the location request object.
57
This lesson shows you how to get the update using the LocationListener callback approach.
Call requestLocationUpdates(), passing it your instance of the GoogleApiClient, the LocationRequest object, and a LocationListener. Define a startLocationUpdates() method, called from the onConnected() callback, as shown in the following code sample: [See the following overhead]
58
@Override public void onConnected(Bundle connectionHint) {
@Override public void onConnected(Bundle connectionHint) { ... if (mRequestingLocationUpdates) { startLocationUpdates(); } } protected void startLocationUpdates() { LocationServices.FusedLocationApi.requestLocationUpdates( mGoogleApiClient, mLocationRequest, this); }
59
Notice that the above code snippet refers to a boolean flag, mRequestingLocationUpdates, used to track whether the user has turned location updates on or off. For more about retaining the value of this flag across instances of the activity, see Save the State of the Activity.
60
Define the Location Update Callback
The fused location provider invokes the LocationListener.onLocationChanged() callback method. The incoming argument is a Location object containing the location's latitude and longitude.
61
The following snippet shows how to implement the LocationListener interface and define the method, then get the timestamp of the location update and display the latitude, longitude and timestamp on your app's user interface: [See the following overhead]
62
public class MainActivity extends ActionBarActivity implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener { ... public void onLocationChanged(Location location) { mCurrentLocation = location; mLastUpdateTime = DateFormat.getTimeInstance().format(new Date()); updateUI(); } private void updateUI() { mLatitudeTextView.setText(String.valueOf(mCurrentLocation.getLatitude())); mLongitudeTextView.setText(String.valueOf(mCurrentLocation.getLongitude())); mLastUpdateTimeTextView.setText(mLastUpdateTime); } }
63
Stop Location Updates Consider whether you want to stop the location updates when the activity is no longer in focus, such as when the user switches to another app or to a different activity in the same app. This can be handy to reduce power consumption, provided the app doesn't need to collect information even when it's running in the background. This section shows how you can stop the updates in the activity's onPause() method.
64
To stop location updates, call removeLocationUpdates(), passing it your instance of the GoogleApiClient object and a LocationListener, as shown in the following code sample: [See the following overhead]
65
@Override protected void onPause() { super
@Override protected void onPause() { super.onPause(); stopLocationUpdates(); } protected void stopLocationUpdates() { LocationServices.FusedLocationApi.removeLocationUpdates( mGoogleApiClient, this); }
66
Use a boolean, mRequestingLocationUpdates, to track whether location updates are currently turned on. In the activity's onResume() method, check whether location updates are currently active, and activate them if not: [See the following overhead]
67
@Override public void onResume() { super
@Override public void onResume() { super.onResume(); if (mGoogleApiClient.isConnected() && !mRequestingLocationUpdates) { startLocationUpdates(); } }
68
Save the State of the Activity
A change to the device's configuration, such as a change in screen orientation or language, can cause the current activity to be destroyed. Your app must therefore store any information it needs to recreate the activity. One way to do this is via an instance state stored in a Bundle object.
69
The following code sample shows how to use the activity's onSaveInstanceState() callback to save the instance state: [See the following overhead]
70
public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putBoolean(REQUESTING_LOCATION_UPDATES_KEY, mRequestingLocationUpdates); savedInstanceState.putParcelable(LOCATION_KEY, mCurrentLocation); savedInstanceState.putString(LAST_UPDATED_TIME_STRING_KEY, mLastUpdateTime); super.onSaveInstanceState(savedInstanceState); }
71
Define an updateValuesFromBundle() method to restore the saved values from the previous instance of the activity, if they're available. Call the method from the activity's onCreate() method, as shown in the following code sample: [See the following overheads]
72
@Override public void onCreate(Bundle savedInstanceState) {
@Override public void onCreate(Bundle savedInstanceState) { ... updateValuesFromBundle(savedInstanceState); } private void updateValuesFromBundle(Bundle savedInstanceState) { if (savedInstanceState != null) { // Update the value of mRequestingLocationUpdates from the Bundle, and // make sure that the Start Updates and Stop Updates buttons are // correctly enabled or disabled. if (savedInstanceState.keySet().contains(REQUESTING_LOCATION_UPDATES_KEY)) { mRequestingLocationUpdates = savedInstanceState.getBoolean( REQUESTING_LOCATION_UPDATES_KEY); setButtonsEnabledState(); }
73
// Update the value of mCurrentLocation from the Bundle and update the // UI to show the correct latitude and longitude. if (savedInstanceState.keySet().contains(LOCATION_KEY)) { // Since LOCATION_KEY was found in the Bundle, we can be sure that // mCurrentLocationis not null. mCurrentLocation = savedInstanceState.getParcelable(LOCATION_KEY); } // Update the value of mLastUpdateTime from the Bundle and update the UI. if (savedInstanceState.keySet().contains(LAST_UPDATED_TIME_STRING_KEY)) { mLastUpdateTime = savedInstanceState.getString( LAST_UPDATED_TIME_STRING_KEY); } updateUI(); } }
74
For more about saving instance state, see the Android Activity class reference.
Note: For a more persistent storage, you can store the user's preferences in your app's SharedPreferences. Set the shared preference in your activity's onPause() method, and retrieve the preference in onResume().
75
For more information about saving preferences, read Saving Key-Value Sets.
The next lesson, Displaying a Location Address, shows you how to display the street address for a given location.
76
Displaying a Location Address
The lessons Getting the Last Known Location and Receiving Location Updates describe how to get the user's location in the form of a Location object that contains latitude and longitude coordinates. 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.
77
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 geographic coordinates (latitude/longitude) of the location. Using the Geocoder class in the Android framework location APIs, you can convert an address to the corresponding geographic coordinates. This process is called geocoding.
78
Alternatively, you can convert a geographic location to an address
Alternatively, you can convert a geographic location to an address. The address lookup feature is also known as reverse geocoding. This lesson shows you how to use the getFromLocation() method to convert a geographic location to an address. The method returns an estimated street address corresponding to a given latitude and longitude.
79
Get a Geographic Location
The last known location of the device is a useful starting point for the address lookup feature. The lesson on Getting the Last Known Location shows you how to use the getLastLocation() method provided by the fused location provider to find the latest location of the device. To access the fused location provider, you need to create an instance of the Google Play services API client.
80
To learn how to connect your client, see Connect to Google Play Services.
In order for the fused location provider to retrieve a precise street address, set the location permission in your app manifest to ACCESS_FINE_LOCATION, as shown in the following example: [See the following overhead]
81
<manifest xmlns:android="http://schemas. android
<manifest xmlns:android=" package="com.google.android.gms.location.sample.locationupdates" > <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> </manifest>
82
Define an Intent Service to Fetch the Address
The getFromLocation() method provided by the Geocoder class accepts a latitude and longitude, and returns a list of addresses. The method is synchronous, and may take a long time to do its work, so you should not call it from the main, user interface (UI) thread of your app. The IntentService class provides a structure for running a task on a background thread.
83
Using this class, you can handle a long-running operation without affecting your UI's responsiveness. Note that the AsyncTask class also allows you to perform background operations, but it's designed for short operations. An AsyncTask shouldn't keep a reference to the UI if the activity is recreated, for example when the device is rotated.
84
In contrast, an IntentService doesn't need to be cancelled when the activity is rebuilt.
Define a FetchAddressIntentService class that extends IntentService. This class is your address lookup service. The intent service handles an intent asynchronously on a worker thread, and stops itself when it runs out of work.
85
The intent extras provide the data needed by the service, including a Location object for conversion to an address, and a ResultReceiver object to handle the results of the address lookup. The service uses a Geocoder to fetch the address for the location, and sends the results to the ResultReceiver.
86
Define the Intent Service in your App Manifest
Add an entry to your app manifest defining the intent service: <manifest xmlns:android=" package="com.google.android.gms.location.sample.locationaddress" > <application ... <service android:name=".FetchAddressIntentService" android:exported="false"/> </application> ... </manifest>
87
Note: The <service> element in the manifest doesn't need to include an intent filter, because your main activity creates an explicit intent by specifying the name of the class to use for the intent.
88
Create a Geocoder The process of converting a geographic location to an address is called reverse geocoding. To perform the main work of the intent service, that is, your reverse geocoding request, implement onHandleIntent() within the FetchAddressIntentService class. Create a Geocoder object to handle the reverse geocoding.
89
A locale represents a specific geographical or linguistic region.
Locale objects are used to adjust the presentation of information, such as numbers or dates, to suit the conventions in the region represented by the locale. Pass a Locale object to the Geocoder object, to ensure that the resulting address is localized to the user's geographic region. [See the following overhead]
90
@Override protected void onHandleIntent(Intent intent) { Geocoder geocoder = new Geocoder(this, Locale.getDefault()); ... }
91
Retrieve the street address data
The next step is to retrieve the street address from the geocoder, handle any errors that may occur, and send the results back to the activity that requested the address. To report the results of the geocoding process, you need two numeric constants that indicate success or failure. Define a Constants class to contain the values, as shown in this code snippet: [See the following overhead]
92
public final class Constants { public static final int SUCCESS_RESULT = 0; public static final int FAILURE_RESULT = 1; public static final String PACKAGE_NAME = "com.google.android.gms.location.sample.locationaddress"; public static final String RECEIVER = PACKAGE_NAME + ".RECEIVER"; public static final String RESULT_DATA_KEY = PACKAGE_NAME + ".RESULT_DATA_KEY"; public static final String LOCATION_DATA_EXTRA = PACKAGE_NAME + ".LOCATION_DATA_EXTRA"; }
93
To get a street address corresponding to a geographical location, call getFromLocation(), passing it the latitude and longitude from the location object, and the maximum number of addresses you want returned. In this case, you want just one address. The geocoder returns an array of addresses.
94
If no addresses were found to match the given location, it returns an empty list.
If there is no backend geocoding service available, the geocoder returns null. Check for the following errors as shown in the code sample below. If an error occurs, place the corresponding error message in the errorMessage variable, so you can send it back to the requesting activity:
95
No location data provided - The intent extras do not include the Location object required for reverse geocoding. Invalid latitude or longitude used - The latitude and/or longitude values provided in the Location object are invalid. No geocoder available - The background geocoding service is not available, due to a network error or IO exception. Sorry, no address found - The geocoder could not find an address for the given latitude/longitude.
96
To get the individual lines of an address object, use the getAddressLine() method provided by the Address class. Then join the lines into a list of address fragments ready to return to the activity that requested the address. To send the results back to the requesting activity, call the deliverResultToReceiver() method (defined in Return the address to the requestor).
97
The results consist of the previously-mentioned numeric success/failure code and a string.
In the case of a successful reverse geocoding, the string contains the address. In the case of a failure, the string contains the error message, as shown in the code sample below: [See the following overheads]
98
@Override protected void onHandleIntent(Intent intent) { String errorMessage = ""; // Get the location passed to this service through an extra. Location location = intent.getParcelableExtra( Constants.LOCATION_DATA_EXTRA); ... List<Address> addresses = null; try { addresses = geocoder.getFromLocation( location.getLatitude(), location.getLongitude(), // In this sample, get just a single address. 1); } catch (IOException ioException) { // Catch network or other I/O problems. errorMessage = getString(R.string.service_not_available); Log.e(TAG, errorMessage, ioException); } catch (IllegalArgumentException illegalArgumentException) { // Catch invalid latitude or longitude values. errorMessage = getString(R.string.invalid_lat_long_used); Log.e(TAG, errorMessage + ". " + "Latitude = " + location.getLatitude() + ", Longitude = " + location.getLongitude(), illegalArgumentException); }
99
// Handle case where no address was found
// Handle case where no address was found. if (addresses == null || addresses.size() == 0) { if (errorMessage.isEmpty()) { errorMessage = getString(R.string.no_address_found); Log.e(TAG, errorMessage); } deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage); } else { Address address = addresses.get(0); ArrayList<String> addressFragments = new ArrayList<String>(); // Fetch the address lines using getAddressLine, // join them, and send them to the thread. for(int i = 0; i < address.getMaxAddressLineIndex(); i++) { addressFragments.add(address.getAddressLine(i)); } Log.i(TAG, getString(R.string.address_found)); deliverResultToReceiver(Constants.SUCCESS_RESULT, TextUtils.join(System.getProperty("line.separator"), addressFragments)); } }
100
Return the address to the requestor
The final thing the intent service must do is send the address back to a ResultReceiver in the activity that started the service. The ResultReceiver class allows you to send a numeric result code as well as a message containing the result data. The numeric code is useful for reporting the success or failure of the geocoding request. In the case of a successful reverse geocoding, the message contains the address. In the case of a failure, the message contains some text describing the reason for failure.
101
You have already retrieved the address from the geocoder, trapped any errors that may occur, and called the deliverResultToReceiver() method. Now you need to define the deliverResultToReceiver() method that sends a result code and message bundle to the result receiver. For the result code, use the value that you've passed to the deliverResultToReceiver() method in the resultCode parameter.
102
To construct the message bundle, concatenate the RESULT_DATA_KEY constant from your Constants class (defined in Retrieve the street address data) and the value in the message parameter passed to the deliverResultToReceiver() method, as shown in the following sample: [See the following overhead]
103
public class FetchAddressIntentService extends IntentService { protected ResultReceiver mReceiver; ... private void deliverResultToReceiver(int resultCode, String message) { Bundle bundle = new Bundle(); bundle.putString(Constants.RESULT_DATA_KEY, message); mReceiver.send(resultCode, bundle); } }
104
Start the Intent Service
The intent service, as defined in the previous section, runs in the background and is responsible for fetching the address corresponding to a given geographic location. When you start the service, the Android framework instantiates and starts the service if it isn't already running, and creates a process if needed.
105
If the service is already running then it remains running.
Because the service extends IntentService, it shuts down automatically when all intents have been processed. Start the service from your app's main activity, and create an Intent to pass data to the service. You need an explicit intent, because you want only your service to respond to the intent. For more information, see Intent Types.
106
To create an explicit intent, specify the name of the class to use for the service: FetchAddressIntentService.class. Pass two pieces of information in the intent extras:
107
A ResultReceiver to handle the results of the address lookup.
A Location object containing the latitude and longitude that you want to convert to an address. The following code sample shows you how to start the intent service: [See the following overhead]
108
public class MainActivity extends ActionBarActivity implements ConnectionCallbacks, OnConnectionFailedListener { protected Location mLastLocation; private AddressResultReceiver mResultReceiver; ... protected void startIntentService() { Intent intent = new Intent(this, FetchAddressIntentService.class); intent.putExtra(Constants.RECEIVER, mResultReceiver); intent.putExtra(Constants.LOCATION_DATA_EXTRA, mLastLocation); startService(intent); } }
109
Call the above startIntentService() method when the user takes an action that requires a geocoding address lookup. For example, the user may press a Fetch address button on your app's UI.
110
Before starting the intent service, you need to check that the connection to Google Play services is present. The following code snippet shows the call to the startIntentService() method in the button handler: [See the following overhead]
111
public void fetchAddressButtonHandler(View view) { // Only start the service to fetch the address if GoogleApiClient is // connected. if (mGoogleApiClient.isConnected() && mLastLocation != null) { startIntentService(); } // If GoogleApiClient isn't connected, process the user's request by // setting mAddressRequested to true. Later, when GoogleApiClient connects, // launch the service to fetch the address. As far as the user is // concerned, pressing the Fetch Address button // immediately kicks off the process of getting the address. mAddressRequested = true; updateUIWidgets(); }
112
You must also start the intent service when the connection to Google Play services is established, if the user has already clicked the button on your app's UI. The following code snippet shows the call to the startIntentService() method in the onConnected() callback provided by the Google API Client: [See the following overhead]
113
public class MainActivity extends ActionBarActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... public void onConnected(Bundle connectionHint) { // Gets the best and most recent location currently available, // which may be null in rare cases when a location is not available. mLastLocation = LocationServices.FusedLocationApi.getLastLocation( mGoogleApiClient); if (mLastLocation != null) { // Determine whether a Geocoder is available. if (!Geocoder.isPresent()) { Toast.makeText(this, R.string.no_geocoder_available, Toast.LENGTH_LONG).show(); return; } if (mAddressRequested) { startIntentService(); } } } }
114
Receive the Geocoding Results
The intent service has handled the geocoding request, and uses a ResultReceiver to return the results to the activity that made the request. In the activity that makes the request, define an AddressResultReceiver that extends ResultReceiver to handle the response from FetchAddressIntentService.
115
The result includes a numeric result code (resultCode) as well as a message containing the result data (resultData). If the reverse geocoding process was successful, the resultData contains the address.
116
In the case of a failure, the resultData contains text describing the reason for failure.
For details of the possible errors, see Return the address to the requestor. Override the onReceiveResult() method to handle the results delivered to the result receiver, as shown in the following code sample: [See the following overhead]
117
public class MainActivity extends ActionBarActivity implements ConnectionCallbacks, OnConnectionFailedListener { ... class AddressResultReceiver extends ResultReceiver { public AddressResultReceiver(Handler handler) { super(handler); } protected void onReceiveResult(int resultCode, Bundle resultData) { // Display the address string // or an error message sent from the intent service. mAddressOutput = resultData.getString(Constants.RESULT_DATA_KEY); displayAddressOutput(); // Show a toast message if an address was found. if (resultCode == Constants.SUCCESS_RESULT) { showToast(getString(R.string.address_found)); } } } }
118
Creating and Monitoring Geofences
Geofencing combines awareness of the user's current location with awareness of the user's proximity to locations that may be of interest. To mark a location of interest, you specify its latitude and longitude. To adjust the proximity for the location, you add a radius. The latitude, longitude, and radius define a geofence, creating a circular area, or fence, around the location of interest.
119
You can have multiple active geofences, with a limit of 100 per device user.
For each geofence, you can ask Location Services to send you entrance and exit events, or you can specify a duration within the geofence area to wait, or dwell, before triggering an event. You can limit the duration of any geofence by specifying an expiration duration in milliseconds. After the geofence expires, Location Services automatically removes it.
121
This lesson shows you how to add and remove geofences, and then listen for geofence transitions using an IntentService. We recommend upgrading existing apps to use the LocationServices class, which contains the GeofencingApi interface. The LocationServices class replaces the LocationClient (deprecated).
122
Set up for Geofence Monitoring
The first step in requesting geofence monitoring is to request the necessary permission. To use geofencing, your app must request ACCESS_FINE_LOCATION. To request this permission, add the following element as a child element of the <manifest> element in your app manifest: <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
123
This element must be a child of the <application> element:
If you want to use an IntentService to listen for geofence transitions, add an element specifying the service name. This element must be a child of the <application> element: <application android:allowBackup="true"> ... <service android:name=".GeofenceTransitionsIntentService"/> <application/>
124
To access the location APIs, you need to create an instance of the Google Play services API client.
To learn how to connect your client, see Connect to Google Play Services.
125
Create and Add Geofences
Your app needs to create and add geofences using the location API's builder class for creating Geofence objects, and the convenience class for adding them. Also, to handle the intents sent from Location Services when geofence transitions occur, you can define a PendingIntent as shown in this section. Note: On single-user devices, there is a limit of 100 geofences per app. For multi-user devices, the limit is 100 geofences per app per device user.
126
Create geofence objects
First, use Geofence.Builder to create a geofence, setting the desired radius, duration, and transition types for the geofence. For example, to populate a list object named mGeofenceList: [See the following overhead]
127
mGeofenceList. add(new Geofence
mGeofenceList.add(new Geofence.Builder() // Set the request ID of the geofence. This is a string to identify this // geofence. .setRequestId(entry.getKey()) .setCircularRegion( entry.getValue().latitude, entry.getValue().longitude, Constants.GEOFENCE_RADIUS_IN_METERS ) .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS) .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT) .build());
128
This example pulls data from a constants file
This example pulls data from a constants file. In actual practice, apps might dynamically create geofences based on the user's location.
129
Specify geofences and initial triggers
The following snippet uses the GeofencingRequest class and its nested GeofencingRequestBuilder class to specify the geofences to monitor and to set how related geofence events are triggered: private GeofencingRequest getGeofencingRequest() { GeofencingRequest.Builder builder = new GeofencingRequest.Builder(); builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER); builder.addGeofences(mGeofenceList); return builder.build(); }
130
This example shows the use of two geofence triggers.
The GEOFENCE_TRANSITION_ENTER transition triggers when a device enters a geofence, and the GEOFENCE_TRANSITION_EXIT transition triggers when a device exits a geofence. Specifying INITIAL_TRIGGER_ENTER tells Location services that GEOFENCE_TRANSITION_ENTER should be triggered if the the device is already inside the geofence.
131
In many cases, it may be preferable to use instead INITIAL_TRIGGER_DWELL, which triggers events only when the user stops for a defined duration within a geofence. This approach can help reduce "alert spam" resulting from large numbers notifications when a device briefly enters and exits geofences.
132
Another strategy for getting best results from your geofences is to set a minimum radius of 100 meters. This helps account for the location accuracy of typical Wi-Fi networks, and also helps reduce device power consumption.
133
Define an Intent for geofence transitions
The Intent sent from Location Services can trigger various actions in your app, but you should not have it start an activity or fragment, because components should only become visible in response to a user action. In many cases, an IntentService is a good way to handle the intent.
134
An IntentService can post a notification, do long-running background work, send intents to other services, or send a broadcast intent. The following snippet shows how to define a PendingIntent that starts an IntentService: [See the following overhead]
135
public class MainActivity extends FragmentActivity {
public class MainActivity extends FragmentActivity { ... private PendingIntent getGeofencePendingIntent() { // Reuse the PendingIntent if we already have it. if (mGeofencePendingIntent != null) { return mGeofencePendingIntent; } Intent intent = new Intent(this, GeofenceTransitionsIntentService.class); // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when // calling addGeofences() and removeGeofences(). return PendingIntent.getService(this, 0, intent, PendingIntent. FLAG_UPDATE_CURRENT); }
136
Add geofences To add geofences, use the GeoencingApi.addGeofences() method. Provide the Google API client, the GeofencingRequest object, and the PendingIntent. The following snippet, which processes the results in onResult(), assumes that the main activity implements ResultCallback: [See the following overhead]
137
public class MainActivity extends FragmentActivity {. LocationServices
public class MainActivity extends FragmentActivity { ... LocationServices.GeofencingApi.addGeofences( mGoogleApiClient, getGeofencingRequest(), getGeofencePendingIntent() ).setResultCallback(this);
138
Handle Geofence Transitions
When Location Services detects that the user has entered or exited a geofence, it sends out the Intent contained in the PendingIntent you included in the request to add geofences. This Intent is received by a service like GeofenceTransitionsIntentService, which obtains the geofencing event from the intent, determines the type of Geofence transition(s), and determines which of the defined geofences was triggered. It then sends a notification as the output.
139
The following snippet shows how to define an IntentService that posts a notification when a geofence transition occurs. When the user clicks the notification, the app's main activity appears: [See the following overheads]
140
public class GeofenceTransitionsIntentService extends IntentService {
public class GeofenceTransitionsIntentService extends IntentService { ... protected void onHandleIntent(Intent intent) { GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); if (geofencingEvent.hasError()) { String errorMessage = GeofenceErrorMessages.getErrorString(this, geofencingEvent.getErrorCode()); Log.e(TAG, errorMessage); return; } // Get the transition type. int geofenceTransition = geofencingEvent.getGeofenceTransition(); // Test that the reported transition was of interest. if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
141
// Get the geofences that were triggered
// Get the geofences that were triggered. A single event can trigger // multiple geofences. List triggeringGeofences = geofencingEvent.getTriggeringGeofences(); // Get the transition details as a String. String geofenceTransitionDetails = getGeofenceTransitionDetails( this, geofenceTransition, triggeringGeofences ); // Send notification and log the transition details. sendNotification(geofenceTransitionDetails); Log.i(TAG, geofenceTransitionDetails); } else { // Log the error. Log.e(TAG, getString(R.string.geofence_transition_invalid_type, geofenceTransition)); } }
142
After detecting the transition event via the PendingIntent, this IntentService gets the geofence transition type and tests whether it is one of the events the app uses to trigger notifications -- either GEOFENCE_TRANSITION_ENTER or GEOFENCE_TRANSITION_EXIT in this case. The service then sends a notification and logs the transition details.
143
Stop Geofence Monitoring
Stopping geofence monitoring when it is no longer needed or desired can help save battery power and CPU cycles on the device. You can stop geofence monitoring in the main activity used to add and remove geofences; removing a geofence stops it immediately.
144
The API provides methods to remove geofences either by request IDs, or by removing geofences associated with a given PendingIntent. The following snippet removes geofences by PendingIntent, stopping all further notification when the device enters or exits previously added geofences: [See the following overhead]
145
LocationServices. GeofencingApi
LocationServices.GeofencingApi.removeGeofences( mGoogleApiClient, // This is the same pending intent that was used in addGeofences(). getGeofencePendingIntent() ).setResultCallback(this); // Result processed in onResult(). }
146
You can combine geofencing with other location-aware features, such as periodic location updates.
For more information, see the other lessons in this class.
147
Use Best Practices for Geofencing
This section outlines recommendations for using geofencing with the location APIs for Android.
148
Reduce power consumption
You can use the following techniques to optimize power consumption in your apps that use geofencing: Set the notification responsiveness to a higher value. Doing so improves power consumption by increasing the latency of geofence alerts. For example, if you set a responsiveness value of five minutes your app only checks for an entrance or exit alert once every five minutes.
149
Setting lower values does not necessarily mean that users will be notified within that time period (for example, if you set a value of 5 seconds it may take a bit longer than that to receive the alert). Use a larger geofence radius for locations where a user spends a significant amount of time, such as home or work. While a larger radius doesn't directly reduce power consumption, it reduces the frequency at which the app checks for entrance or exit, effectively lowering overall power consumption.
150
Choose the optimal radius for your geofence
For best results, the minimium radius of the geofence should be set between meters. When Wi-Fi is available location accuracy is usually between meters. When indoor location is available, the accuracy range can be as small as 5 meters.
151
Unless you know indoor location is available inside the geofence, assume that Wi-Fi location accuracy is about 50 meters. When Wi-Fi location is not available (for example, when you are driving in rural areas) the location accuracy degrades. The accuracy range can be as large as several hundred meters to several kilometers. In cases like this, you should create geofences using a larger radius.
152
Use the dwell transition type to reduce alert spam
If you receive a large number of alerts when driving briefly past a geofence, the best way to reduce the alerts is to use a transition type of GEOFENCE_TRANSITION_DWELL instead of GEOFENCE_TRANSITION_ENTER. This way, the dwelling alert is sent only when the user stops inside a geofence for a given period of time. You can choose the duration by setting a loitering delay.
153
Re-register geofences only when required
Registered geofences are kept in the com.google.process.location process owned by the com.google.android.gms package. The app doesn’t need to do anything to handle the following events, because the system restores geofences after these events: Google Play services is upgraded. Google Play services is killed and restarted by the system due resource restriction. The location process crashes.
154
The app must re-register geofences if they're still needed after the following events, since the system cannot recover the geofences in the following cases: The device is rebooted. The app should listen for the device's boot complete action, and then re- register the geofences required.
155
The app is uninstalled and re-installed.
The app's data is cleared. Google Play services data is cleared. The app has received a GEOFENCE_NOT_AVAILABLE alert. This typically happens after NLP (Android's Network Location Provider) is disabled.
156
Troubleshoot the Geofence Entrance Event
If geofences are not being triggered when the device enters a geofence (the GEOFENCE_TRANSITION_ENTER alert isn’t triggered), first ensure that your geofences are registered properly as described in this guide. Here are some possible reasons for alerts not working as expected:
157
Accurate location is not available inside your geofence or your geofence is too small.
On most devices, the geofence service uses only network location for geofence triggering. The service uses this approach because network location consumes much less power, it takes less time to get discrete locations, and most importantly it’s available indoors.
158
Starting with Google Play services 3
Starting with Google Play services 3.2, the geofence service calculates the overlapping ratio of the location circle and the geofence circle and only generates the entrance alert when the ratio is at least 85% for a bigger geofence or 75% for a smaller geofence. For an exit alert, the ratio threshold used is 15% or 25%. Any ratio between these thresholds makes the geofence service mark the geofence state as INSIDE_LOW_CONFIDENCE or OUTSIDE_LOW_CONFIDENCE and no alert is sent.
159
Wi-Fi is turned off on the device.
Having Wi-Fi on can significantly improve the location accuracy, so if Wi-Fi is turned off, your application might never get geofence alerts depending on several settings including the radius of the geofence, the device model, or the Android version. Starting from Android 4.3 (API level 18), we added the capability of “Wi-Fi scan only mode” which allows users to disable Wi-Fi but still get good network location.
160
It’s good practice to prompt the user and provide a shortcut for the user to enable Wi-Fi or Wi-Fi scan only mode if both of them are disabled. Use SettingsApi to ensure that the device's system settings are properly configured for optimal location detection.
161
There is no reliable network connectivity inside your geofence.
If there is no reliable data connection, alerts might not be generated. This is because the geofence service depends on the network location provider which in turn requires a data connection.
162
Alerts can be late. The geofence service does not continuously query for location, so expect some latency when receiving alerts. Usually the latency is less than 2 minutes, even less when the device has been moving. If the device has been stationary for a significant period of time, the latency may increase (up to 6 minutes).
163
Summary and Mission You may want to include location as part of your assignment or project
164
The End
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.