If you haven’t set up Google Play services yet, please follow this tutorial.
Permissions
First, update your AndroidManifest.xml file to request the permissions for accessing locations:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
The two permissions allow you to control the accuracy of the requested locations, and you don’t have to request both for your app. If you only request the coarse location permission, the fetched location will be obfuscated. However, if you want to use the geofencing feature, you must request the ACCESS_FINE_LOCATION
permission.
Connect Location Client
With the new GoogleApiClient class, you can connect all needed services at once, and Google Play services will handle all the permission requests, etc.:
public class MainActivity extends Activity
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private GoogleApiClient mGoogleClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// you can also add more APIs and scopes here
mGoogleClient = new GoogleApiClient.Builder(this, this, this)
.addApi(LocationServices.API)
.build();
}
@Override
protected void onStart() {
super.onStart();
mGoogleClient.connect();
}
@Override
protected void onStop() {
mGoogleClient.disconnect();
super.onStop();
}
@Override
public void onConnected(Bundle connectionHint) {
// this callback will be invoked when all specified services are connected
}
@Override
public void onConnectionSuspended(int cause) {
// this callback will be invoked when the client is disconnected
// it might happen e.g. when Google Play service crashes
// when this happens, all requests are canceled,
// and you must wait for it to be connected again
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
// this callback will be invoked when the connection attempt fails
if (connectionResult.hasResolution()) {
// Google Play services can fix the issue
// e.g. the user needs to enable it, updates to latest version
// or the user needs to grant permissions to it
try {
connectionResult.startResolutionForResult(this, 0);
} catch (IntentSender.SendIntentException e) {
// it happens if the resolution intent has been canceled,
// or is no longer able to execute the request
}
} else {
// Google Play services has no idea how to fix the issue
}
}
}
Access Current Location
Once connected, you can easily fetch the current location:
// fetch the current location
Location location = LocationServices.FusedLocationApi.getLastLocation(mGoogleClient);
Note that this getLastLocation()
method might return null in case location is not available, though this happens very rarely. Also, it might return a location that is a bit old, so the client should check it manually.
Listen to Location Updates
Let’s extend the above code snippet:
public class MainActivity extends Activity
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
LocationListener {
...
@Override
public void onConnected(Bundle dataBundle) {
...
// start listening to location updates
// this is suitable for foreground listening,
// with the onLocationChanged() invoked for location updates
LocationRequest locationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY)
.setFastestInterval(5000L)
.setInterval(10000L)
.setSmallestDisplacement(75.0F);
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleClient, locationRequest, this);
}
@Override
public void onLocationChanged(Location location) {
// this callback is invoked when location updates
}
...
}
The difference between setFastestInterval()
and setInterval()
is:
- If the location updates is retrieved by other apps (or other location request in your app), your
onLocationChanged()
callback here won’t be called more frequently than the time set bysetFastestInterval()
. - On the other hand, the location client will actively try to get location updates at the interval set by
setInterval()
, which has a direct impact on the power consumption of your app.
Then how about background location tracking? Do I need to implement a long-running Service
myself? The answer is simply, no.
@Override
public void onConnected(Bundle dataBundle) {
...
LocationRequest locationRequest = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY)
.setFastestInterval(5000L)
.setInterval(10000L)
.setSmallestDisplacement(75.0F);
PendingIntent pendingIntent = PendingIntent.getService(this, 0,
new Intent(this, MyLocationHandler.class),
PendingIntent.FLAG_UPDATE_CURRENT);
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleClient, locationRequest, pendingIntent);
}
With this, your listener (it can be an IntentService
, or a BroadcastReceiver
) as defined in the PendingIntent
will be triggered even if your app is killed by the system. The location updated will be sent with key FusedLocationProviderApi.KEY_LOCATION_CHANGED
and a Location
object as the value in the Intent
:
public class MyLocationHandler extends IntentService {
public MyLocationHandler() {
super("net.zionsoft.example.MyLocationHandler");
}
@Override
protected void onHandleIntent(Intent intent) {
final Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
// happy playing with your location
}
}
Geofencing
With geofencing, your app can be notified when the device enters, stays in, or exits a defined area. Please note that geofencing requires ACCESS_FINE_LOCATION
. Again, let’s keep extending the above sample:
public class MainActivity extends Activity
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
...
@Override
public void onConnected(Bundle dataBundle) {
...
// adds geofencing
ArrayList<Geofence> geofences = new ArrayList<Geofence>();
geofences.add(new Geofence.Builder()
.setRequestId("unique-geofence-id")
.setCircularRegion(60.1708, 24.9375, 1000)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER
| Geofence.GEOFENCE_TRANSITION_DWELL
| Geofence.GEOFENCE_TRANSITION_EXIT)
.setLoiteringDelay(30000)
.build());
PendingIntent pendingIntent = PendingIntent.getService(this, 0,
new Intent(this, MyGeofenceHandler.class),
PendingIntent.FLAG_UPDATE_CURRENT);
LocationServices.GeofencingApi.addGeofences(
mGoogleClient, geofences, pendingIntent);
}
...
}
Here, the loitering delay means the GEOFENCE_TRANSITION_DWELL
will be notified 30 seconds after the device enters the area. There’re also limitations on the number of geofences (100 per app) and pending intents (5 per app) enforced.
When a geofence transition is triggered, you can find more details easily e.g. in an IntentService
:
@Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
...
}
Finally, to remove geofences, you can simply use one of the overloaded LocationServices.GeofencingApi.removeGeofences()
methods.
Mock Locations
To enable mock locations, you must first request the corresponding permission (usually for your debug build only):
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
Then you can enable and set mock location:
@Override
public void onConnected(Bundle dataBundle) {
...
LocationServices.FusedLocationApi.setMockMode(mGoogleClient, true);
LocationServices.FusedLocationApi.setMockLocation(mGoogleClient, mockLocation);
}
To distinguish if the location is a mock one, a key of FusedLocationProviderApi.KEY_MOCK_LOCATION
in the location object’s bundle extra will be set to true.
Once done, please remember to set the mock mode to false. If you forget that, the system will set it to false when your location client is disconnected.
That’s it for today. Happy coding and keep reading.