1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
|
page.title=Receiving Location Updates
trainingnavtop=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>This lesson teaches you to</h2>
<ol>
<li><a href="#Permissions">Request Location Permission</a></li>
<li><a href="#PlayServices">Check for Google Play Services</a></li>
<li><a href="#DefineCallbacks">Define Location Services Callbacks</a></li>
<li><a href="#UpdateParameters">Specify Update Parameters</a></li>
<li><a href="#StartUpdates">Start Location Updates</a></li>
<li><a href="#StopUpdates">Stop Location Updates</a></li>
</ol>
<h2>You should also read</h2>
<ul>
<li>
<a href="{@docRoot}google/play-services/setup.html">Setup Google Play Services SDK</a>
</li>
<li>
<a href="retrieve-current.html">Retrieving the Current Location</a>
</li>
</ul>
<h2>Try it out</h2>
<div class="download-box">
<a href="http://developer.android.com/shareables/training/LocationUpdates.zip" class="button">Download the sample</a>
<p class="filename">LocationUpdates.zip</p>
</div>
</div>
</div>
<p>
If your app does navigation or tracking, you probably want to get the user's
location at regular intervals. While you can do this with
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#getLastLocation()">LocationClient.getLastLocation()</a></code>,
a more direct approach is to request periodic updates from Location Services. In
response, Location Services automatically updates your app with the best available location,
based on the currently-available location providers such as WiFi and GPS.
</p>
<p>
To get periodic location updates from Location Services, you send a request using a location
client. Depending on the form of the request, Location Services either invokes a callback
method and passes in a {@link android.location.Location} object, or issues an
{@link android.content.Intent} 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
parameters you pass to Location Services with the request.
</p>
<!-- Request permission -->
<h2 id="Permissions">Specify App Permissions</h2>
<p>
Apps that use Location Services must request location permissions. Android has two location
permissions, {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION}
and {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION}. The
permission you choose affects the accuracy of the location updates you receive.
For example, If you request only coarse location permission, Location Services obfuscates the
updated location to an accuracy that's roughly equivalent to a city block.
</p>
<p>
Requesting {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} implies
a request for {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION}.
</p>
<p>
For example, to add the coarse location permission to your manifest, insert the following as a
child element of
the
<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code>
element:
</p>
<pre>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
</pre>
<!-- Check for Google Play services -->
<h2 id="PlayServices">Check for Google Play Services</h2>
<p>
Location Services is part of the Google Play services APK. Since it's hard to anticipate the
state of the user's device, you should always check that the APK is installed before you attempt
to connect to Location Services. To check that the APK is installed, call
<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesUtil.html#isGooglePlayServicesAvailable(android.content.Context)">GooglePlayServicesUtil.isGooglePlayServicesAvailable()</a></code>,
which returns one of the
integer result codes listed in the API reference documentation. If you encounter an error,
call
<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesUtil.html#getErrorDialog(int, android.app.Activity, int)">GooglePlayServicesUtil.getErrorDialog()</a></code>
to retrieve localized dialog that prompts users to take the correct action, then display
the dialog in a {@link android.support.v4.app.DialogFragment}. The dialog may allow the
user to correct the problem, in which case Google Play services may send a result back to your
activity. To handle this result, override the method
{@link android.support.v4.app.FragmentActivity#onActivityResult onActivityResult()}
</p>
<p class="note">
<strong>Note:</strong> To make your app compatible with
platform version 1.6 and later, the activity that displays the
{@link android.support.v4.app.DialogFragment} must subclass
{@link android.support.v4.app.FragmentActivity} instead of {@link android.app.Activity}. Using
{@link android.support.v4.app.FragmentActivity} also allows you to call
{@link android.support.v4.app.FragmentActivity#getSupportFragmentManager
getSupportFragmentManager()} to display the {@link android.support.v4.app.DialogFragment}.
</p>
<p>
Since you usually need to check for Google Play services in more than one place in your code,
define a method that encapsulates the check, then call the method before each connection
attempt. The following snippet contains all of the code required to check for Google
Play services:
</p>
<pre>
public class MainActivity extends FragmentActivity {
...
// Global constants
/*
* Define a request code to send to Google Play services
* This code is returned in Activity.onActivityResult
*/
private final static int
CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
...
// Define a DialogFragment that displays the error dialog
public static class ErrorDialogFragment extends DialogFragment {
// Global field to contain the error dialog
private Dialog mDialog;
// Default constructor. Sets the dialog field to null
public ErrorDialogFragment() {
super();
mDialog = null;
}
// Set the dialog to display
public void setDialog(Dialog dialog) {
mDialog = dialog;
}
// Return a Dialog to the DialogFragment.
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return mDialog;
}
}
...
/*
* Handle results returned to the FragmentActivity
* by Google Play services
*/
@Override
protected void onActivityResult(
int requestCode, int resultCode, Intent data) {
// Decide what to do based on the original request code
switch (requestCode) {
...
case CONNECTION_FAILURE_RESOLUTION_REQUEST :
/*
* If the result code is Activity.RESULT_OK, try
* to connect again
*/
switch (resultCode) {
case Activity.RESULT_OK :
/*
* Try the request again
*/
...
break;
}
...
}
...
}
...
private boolean servicesConnected() {
// Check that Google Play services is available
int resultCode =
GooglePlayServicesUtil.
isGooglePlayServicesAvailable(this);
// If Google Play services is available
if (ConnectionResult.SUCCESS == resultCode) {
// In debug mode, log the status
Log.d("Location Updates",
"Google Play services is available.");
// Continue
return true;
// Google Play services was not available for some reason
} else {
// Get the error code
int errorCode = connectionResult.getErrorCode();
// Get the error dialog from Google Play services
Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
errorCode,
this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
// If Google Play services can provide an error dialog
if (errorDialog != null) {
// Create a new DialogFragment for the error dialog
ErrorDialogFragment errorFragment =
new ErrorDialogFragment();
// Set the dialog in the DialogFragment
errorFragment.setDialog(errorDialog);
// Show the error dialog in the DialogFragment
errorFragment.show(
getSupportFragmentManager(),
"Location Updates");
}
}
}
...
}
</pre>
<p>
Snippets in the following sections call this method to verify that Google Play services is
available.
</p>
<!--
Define Location Services Callbacks
-->
<h2 id="DefineCallbacks">Define Location Services Callbacks</h2>
<p>
Before you request location updates, you must first implement the interfaces that Location
Services uses to communicate connection status to your app:
</p>
<dl>
<dt>
<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.ConnectionCallbacks.html">ConnectionCallbacks</a></code>
</dt>
<dd>
Specifies methods that Location Services calls when a location client is connected or
disconnected.
</dd>
<dt>
<code><a href="{@docRoot}reference/com/google/android/gms/common/GooglePlayServicesClient.OnConnectionFailedListener.html">OnConnectionFailedListener</a></code>
</dt>
<dd>
Specifies a method that Location Services calls if an error occurs while attempting to
connect the location client. This method uses the previously-defined {@code showErrorDialog}
method to display an error dialog that attempts to fix the problem using Google Play
services.
</dd>
</dl>
<p>
The following snippet shows how to specify the interfaces and define the methods:
</p>
<pre>
public class MainActivity extends FragmentActivity implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener {
...
/*
* Called by Location Services when the request to connect the
* client finishes successfully. At this point, you can
* request the current location or start periodic updates
*/
@Override
public void onConnected(Bundle dataBundle) {
// Display the connection status
Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();
}
...
/*
* Called by Location Services if the connection to the
* location client drops because of an error.
*/
@Override
public void onDisconnected() {
// Display the connection status
Toast.makeText(this, "Disconnected. Please re-connect.",
Toast.LENGTH_SHORT).show();
}
...
/*
* Called by Location Services if the attempt to
* Location Services fails.
*/
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
/*
* Google Play services can resolve some errors it detects.
* If the error has a resolution, try sending an Intent to
* start a Google Play services activity that can resolve
* error.
*/
if (connectionResult.hasResolution()) {
try {
// Start an Activity that tries to resolve the error
connectionResult.startResolutionForResult(
this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
/*
* Thrown if Google Play services canceled the original
* PendingIntent
*/
} catch (IntentSender.SendIntentException e) {
// Log the error
e.printStackTrace();
}
} else {
/*
* If no resolution is available, display a dialog to the
* user with the error.
*/
showErrorDialog(connectionResult.getErrorCode());
}
}
...
}
</pre>
<h3>Define the location update callback</h3>
<p>
Location Services sends location updates to your app either as an {@link android.content.Intent}
or as an argument passed to a callback method you define. This lesson shows you how to get the
update using a callback method, because that pattern works best for most use cases. If you want
to receive updates in the form of an {@link android.content.Intent}, read the lesson
<a href="activity-recognition.html">Recognizing the User's Current Activity</a>, which
presents a similar pattern.
</p>
<p>
The callback method that Location Services invokes to send a location update to your app is
specified in the
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationListener.html">LocationListener</a></code>
interface, in the method
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationListener.html#onLocationChanged(android.location.Location)">onLocationChanged()</a></code>.
The incoming argument is a {@link android.location.Location} object containing the location's
latitude and longitude. The following snippet shows how to specify the interface and define
the method:
</p>
<pre>
public class MainActivity extends FragmentActivity implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener,
LocationListener {
...
// Define the callback method that receives location updates
@Override
public void onLocationChanged(Location location) {
// Report to the UI that the location was updated
String msg = "Updated Location: " +
Double.toString(location.getLatitude()) + "," +
Double.toString(location.getLongitude());
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
...
}
</pre>
<p>
Now that you have the callbacks prepared, you can set up the request for location updates.
The first step is to specify the parameters that control the updates.
</p>
<!-- Specify update parameters -->
<h2 id="UpdateParameters">Specify Update Parameters</h2>
<p>
Location Services allows you to control the interval between updates and the location accuracy
you want, by setting the values in a
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html">LocationRequest</a></code>
object and then sending this object as part of your request to start updates.
</p>
<p>
First, set the following interval parameters:
</p>
<dl>
<dt>
Update interval
</dt>
<dd>
Set by
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html#setInterval(long)">LocationRequest.setInterval()</a></code>.
This method sets the rate in milliseconds at which your app prefers to receive location
updates. If no other apps are receiving updates from Location Services, your app will
receive updates at this rate.
</dd>
<dt>
Fastest update interval
</dt>
<dd>
Set by
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html#setFastestInterval(long)">LocationRequest.setFastestInterval()</a></code>.
This method sets the <b>fastest</b> 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. Location Services sends out updates at the fastest rate that any
app requested by calling
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html#setInterval(long)">LocationRequest.setInterval()</a></code>.
If this rate is faster than your app can handle, you may encounter problems with UI flicker
or data overflow. To prevent this, call
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html#setFastestInterval(long)">LocationRequest.setFastestInterval()</a></code>
to set an upper limit to the update rate.
<p>
Calling
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html#setFastestInterval(long)">LocationRequest.setFastestInterval()</a></code>
also helps to save power. When you request a preferred update rate by calling
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html#setInterval(long)">LocationRequest.setInterval()</a></code>,
and a maximum rate by calling
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html#setFastestInterval(long)">LocationRequest.setFastestInterval()</a></code>,
then your app gets the same update rate as the fastest rate in the system. If other
apps have requested a faster rate, you get the benefit of a faster rate. If no other
apps have a faster rate request outstanding, your app receives updates at the rate you specified
with
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html#setInterval(long)">LocationRequest.setInterval()</a></code>.
</p>
</dd>
</dl>
<p>
Next, set the accuracy parameter. In a foreground app, you need constant location updates with
high accuracy, so use the setting
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationRequest.html#PRIORITY_HIGH_ACCURACY">LocationRequest.PRIORITY_HIGH_ACCURACY</a></code>.
</p>
<p>
The following snippet shows how to set the update interval and accuracy in
{@link android.support.v4.app.FragmentActivity#onCreate onCreate()}:
</p>
<pre>
public class MainActivity extends FragmentActivity implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener,
LocationListener {
...
// Global constants
...
// Milliseconds per second
private static final int MILLISECONDS_PER_SECOND = 1000;
// Update frequency in seconds
public static final int UPDATE_INTERVAL_IN_SECONDS = 5;
// Update frequency in milliseconds
private static final long UPDATE_INTERVAL =
MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN SECONDS;
// The fastest update frequency, in seconds
private static final int FASTEST_INTERVAL_IN_SECONDS = 1;
// A fast frequency ceiling in milliseconds
private static final long FASTEST_INTERVAL =
MILLISECONDS_PER_SECOND * FASTEST_INTERVAL_IN_SECONDS;
...
// Define an object that holds accuracy and frequency parameters
LocationResult mLocationRequest;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create the LocationRequest object
mLocationRequest = LocationRequest.create();
// Use high accuracy
mLocationRequest.setPriority(
LocationRequest.PRIORITY_HIGH_ACCURACY);
// Set the update interval to 5 seconds
mLocationRequest.setInterval(UPDATE_INTERVAL);
// Set the fastest update interval to 1 second
mLocationRequest.setFastestInterval(FASTEST_INTERVAL);
...
}
...
}
</pre>
<p class="note">
<strong>Note:</strong> 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 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.
</p>
<!-- Start Location Updates -->
<h2 id="StartUpdates">Start Location Updates</h2>
<p>
To send the request for location updates, create a location client in
{@link android.support.v4.app.FragmentActivity#onCreate onCreate()}, then connect it and make
the request by calling
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest, com.google.android.gms.location.LocationListener)">requestLocationUpdates()</a></code>.
Since your client must be connected for your app to receive updates, you should
connect the client and make the request in
{@link android.support.v4.app.FragmentActivity#onStart onStart()}. This ensures that you always
have a valid, connected client while your app is visible.
</p>
<p>
Remember that the user may want to turn off location updates for various reasons. You should
provide a way for the user to do this, and you should ensure that you don't start updates in
{@link android.support.v4.app.FragmentActivity#onStart onStart()} if updates were previously
turned off. To track the user's preference, store it in your app's
{@link android.content.SharedPreferences} in
{@link android.support.v4.app.FragmentActivity#onPause onPause()} and retrieve it in
{@link android.support.v4.app.FragmentActivity#onResume onResume()}.
</p>
<p>
The following snippet shows how to set up the client in
{@link android.support.v4.app.FragmentActivity#onCreate onCreate()}, and how to connect it
and request updates in {@link android.support.v4.app.FragmentActivity#onStart onStart()}:
</p>
<pre>
public class MainActivity extends FragmentActivity implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener,
LocationListener {
...
// Global variables
...
LocationClient mLocationClient;
boolean mUpdatesRequested;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Open the shared preferences
mPrefs = getSharedPreferences("SharedPreferences",
Context.MODE_PRIVATE);
// Get a SharedPreferences editor
mEditor = mPrefs.edit();
/*
* Create a new location client, using the enclosing class to
* handle callbacks.
*/
mLocationClient = new LocationClient(this, this, this);
// Start with updates turned off
mUpdatesRequested = false;
...
}
...
@Override
protected void onPause() {
// Save the current setting for updates
mEditor.putBoolean("KEY_UPDATES_ON", mUpdatesRequested);
mEditor.commit();
super.onPause();
}
...
@Override
protected void onStart() {
...
mLocationClient.connect();
}
...
@Override
protected void onResume() {
/*
* Get any previous setting for location updates
* Gets "false" if an error occurs
*/
if (mPrefs.contains("KEY_UPDATES_ON")) {
mUpdatesRequested =
mPrefs.getBoolean("KEY_UPDATES_ON", false);
// Otherwise, turn off location updates
} else {
mEditor.putBoolean("KEY_UPDATES_ON", false);
mEditor.commit();
}
}
...
}
</pre>
<p>
For more information about saving preferences, read
<a href="{@docRoot}training/basics/data-storage/shared-preferences.html">Saving Key-Value Sets</a>.
</p>
<!--
Stop Location Updates
-->
<h2 id="StopUpdates">Stop Location Updates</h2>
<p>
To stop location updates, save the state of the update flag in
{@link android.support.v4.app.FragmentActivity#onPause onPause()}, and stop updates in
{@link android.support.v4.app.FragmentActivity#onStop onStop()} by calling
<code><a href="{@docRoot}reference/com/google/android/gms/location/LocationClient.html#removeLocationUpdates(com.google.android.gms.location.LocationListener)">removeLocationUpdates(LocationListener)</a></code>.
For example:
</p>
<pre>
public class MainActivity extends FragmentActivity implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener,
LocationListener {
...
/*
* Called when the Activity is no longer visible at all.
* Stop updates and disconnect.
*/
@Override
protected void onStop() {
// If the client is connected
if (mLocationClient.isConnected()) {
/*
* Remove location updates for a listener.
* The current Activity is the listener, so
* the argument is "this".
*/
removeLocationUpdates(this);
}
/*
* After disconnect() is called, the client is
* considered "dead".
*/
mLocationClient.disconnect();
super.onStop();
}
...
}
</pre>
<p>
You now have the basic structure of an app that requests and receives periodic location updates.
You can combine the features described in this lesson with the geofencing, activity recognition,
or reverse geocoding features described in other lessons in this class.
</p>
<p>
The next lesson, <a href="display-address.html">Displaying a Location Address</a>, shows you how
to use the current location to display the current street address.
</p>
|