• 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 13/01/2020

Exploitez la géolocalisation, un capteur pas comme les autres

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Sous Android, il existe deux bibliothèques permettant de se géolocaliser :

  • via l'API Android (LocationManager) : c'est le système historique ;

  • via l'API Google Play Services (LocationServices) : c'est le service recommandé par Google.

Bien que la première solution soit Open Source (puisque fournie par le système), Google recommande la seconde, qui est propriétaire. Cette seconde serait plus efficace pour économiser l'énergie tout en fournissant des précisions de position adaptées au besoin.

Nous pourrions discuter longtemps sur les raisons qui poussent Google à encourager l'usage de ce service verrouillé, qui fournit de nombreux services annexes (comme le géo-marketing) et qui se retrouve à récupérer un grand nombre de données personnelles à votre sujet chaque fois qu'une application fait des demandes via ce service, mais l'Internet le fait déjà très bien :ange:.

Nous resterons donc agnostiques sur ce point et essayerons de présenter l'usage des deux API. Cela est d'autant plus facile qu'elles ont un usage très similaire. Ce sont surtout les noms des primitives qui changent.

Ainsi, de manière commune aux deux approches, les principales étapes pour faire un suivi de position sont :

  • Dans le manifest :

    • annoncer la permission de géolocaliser le device ;

    • déclarer les dépendances matérielles nécessaires.

  • Dans le code :

    1. demander à l'utilisateur, durant l'exécution, la permission de le géolocaliser ;

    2. récupérer une référence vers le gestionnaire de localisation ;

    3. enregistrer un écouteur auprès du gestionnaire de localisation avec des critères de précision et de réactualisation ;

    4. penser à désactiver la géolocalisation lors de la mise en pause.

De manière générale, il faut être conscient que la géolocalisation est très couteuse en énergie. Augmenter le taux de rafraichissement de l'information ou sa précision, augmentera aussi sa consommation. Nous verrons que ces différents critères sont configurables, mais que la bibliothèque Google Play Services donne plus de stratégies toutes faites pour la gestion de l'énergie.

À l'extrême de la faible consommation énergétique, il est aussi possible de se contenter d'une information indirecte soit en demandant la dernière position connue (getLastKnownLocationougetLastLocation), soit en disant que l'on ne souhaite être notifié de la position que si une autre tâche demande explicitement à connaître la position actuelle (PASSIVE_PROVIDERouPRIORITY_NO_POWER).

Enfin, notez que pour travailler sur un programme utilisant la géolocalisation, vous pourrez également utiliser le simulateur avec les contrôles étendus, qui vous permettront de simuler la mise à disposition par le système d'informations de géolocalisation (manuellement ou via une trace GPS).

Configuration du Manifest

Il est indispensable de demander l'autorisation à l'utilisateur avant de demander sa géolocalisation. Sinon, l'OS refusera de donner l'information et pourra lever une exception. Depuis Android 5, il est également nécessaire d'annoncer le matériel nécessaire au bon fonctionnement de ce service.

Il existe 2 niveaux de précision :

  • Précision fine : nécessite la permission ACCESS_FINE_LOCATION. Le matérielandroid.hardware.location.gps  est recommandé, même si le réseau permet une localisation de plus en plus fine.

  • précision gros-grain : nécessite la permission ACCESS_COARSE_LOCATION  et le matériel android.hardware.location.network.

À noter que la dépendance matérielle annoncée peut être stricte ou non et que l'ensemble des deux dépendance matérielle sont réunie dans un groupe, permettant de simplifier l'expression des dépendances.

Ainsi, pour faire une géolocalisation gros-grain, on pourra annoncer :

<manifest ... >
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-feature android:name="android.hardware.location.network" />
</manifest>

Tandis que pour permettre une localisation grain fin, on annoncera plutôt :

<manifest ... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />
</manifest>

Enfin, si l'application peut faire les deux, alors on pourra annoncer (la permission fine inclue la permission gros-grain) :

<manifest ... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location" android:required="true"/>
</manifest>

Cas de la bibliothèque Android standard

Le service en charge de la géolocalisation dans l'API standard est android.location.LocationManager. Comme pour les autres capteurs, il ne faut pas instancier cette classe, mais récupérer une référence vers lui via un appel système :

LocationManager androidLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

Ensuite, il est facilement possible, par exemple, de connaître la dernière position connue du système via le GPS :

Location loc = androidLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if(loc != null) {
// Localisation disponible
Toast.makeText(MainActivity.this, "Vous étiez récemment ici : "+loc.getLatitude()+" / "+loc.getLongitude(), Toast.LENGTH_LONG).show();
} else {
// Pas de localisation disponible
}

Cette action étant basée sur les connaissances actuelles du système, elle est instantanée et ne nécessite pas d'utiliser un écouteur. Mais elle ne fonctionne que si le système a déjà connaissance de la position (par exemple parce que vous avez surfé sur maps juste avant). Si vous activez fraichement la géolocalisation après un reboot du système, alors le système ne pourra pas répondre (résultatnull).

À l'inverse, si on cherche à connaître la position réelle actuelle du système, alors cela peut prendre du temps. Il faut donc créer un écouteur de type LocationListener et l'enregistrer auprès du système pour être notifié lorsqu'une information de localisation sera disponible. Dans l'exemple suivant, la demande est faite pour que la localisation attendue soit de type gros-grain (NETWORK_PROVIDER) :

LocationListener androidLocationListener = new LocationListener() {
public void onLocationChanged(Location loc) {
Toast.makeText(
MainActivity.this,
"Vous êtes ici : "+
loc.getLatitude()+
" / "+
loc.getLongitude(),
Toast.LENGTH_LONG).show();
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
androidLocationManager.requestSingleUpdate(
LocationManager.NETWORK_PROVIDER,
androidLocationListener,
null);

Enfin, s'abonner auprès du service pour être notifié de l'évolution de la position est très similaire, puisqu'il suffit de préciser la fréquence de rafraichissement souhaitée, ainsi que la distance minimum parcourue avant notification :

LocationListener androidLocationListener = new LocationListener() {
public void onLocationChanged(Location loc) {
Toast.makeText(MainActivity.this, "Vous bougez, mois vous êtes ici : "+loc.getLatitude()+" / "+loc.getLongitude(), Toast.LENGTH_LONG).show();
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
androidLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000, // en millisecondes
50, // en mètres
androidLocationListener);

Bien sûr, n'oubliez pas de vous désenregistrer de ce service lors de la mise en pause de l'activité :

protected void onPause() {
super.onPause();
if(androidLocationListener!=null) {
if (androidLocationManager == null) {
androidLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
}
androidLocationManager.removeUpdates(androidLocationListener);
androidLocationManager=null;
androidLocationListener=null;
}
}

Ainsi, une fois remis bout à bout, une routine complète qui serait notifiée des évolutions de position serait :

private LocationManager androidLocationManager;
private LocationListener androidLocationListener;
private final static int REQUEST_CODE_UPDATE_LOCATION=42;
public void androidUpdateLocation(){
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_CODE_UPDATE_LOCATION);
} else {
androidLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
androidLocationListener = new LocationListener() {
public void onLocationChanged(Location loc) {
Toast.makeText(MainActivity.this, "Vous bougez, mois vous êtes ici : "+loc.getLatitude()+" / "+loc.getLongitude(), Toast.LENGTH_LONG).show();
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
androidLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000, // en millisecondes
50, // en mètres
androidLocationListener);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_UPDATE_LOCATION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
androidUpdateLocation();
} else {
Toast.makeText(MainActivity.this, "Permission refusée.", Toast.LENGTH_LONG).show();
}
return;
}
}
@Override
protected void onPause() {
super.onPause();
if(androidLocationListener!=null) {
if (androidLocationManager == null) {
androidLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
}
androidLocationManager.removeUpdates(androidLocationListener);
androidLocationManager=null;
androidLocationListener=null;
}
}

Cas de la bibliothèque Google Play Services

Pour utiliser la bibliothèque Google Play Services, il faut ajouter cette dépendance à votre application. Dans le fichier build.gradle  lié à votre application (module : app) vous devrez préciser :

dependencies {
compile 'com.google.android.gms:play-services-location:11.0.2'
}

Ensuite, le gestionnaire de localisation à utiliser pour obtenir la géo-localisation via cette bibliothèque est com.google.android.gms.location.FusedLocationProviderClient. On en récupère une instance via com.google.android.gms.location.LocationServices en lui fournissant une référence vers le contexte actuel :

FusedLocationProviderClient locationClient = LocationServices.getFusedLocationProviderClient(MainActivity.this);

Contrairement à l'API Android, tous les moyens permettant récupérer la localisation via l'API Google Play Services sont asynchrones et nécessitent de passer par un callback. Ainsi, même récupérer la dernière position nécessite ce mécanisme. L'avantage est que la réponse donnée est nécessairement fiable. L'inconvénient est que cette demande est passive. Ainsi, si aucune autre application ne demande la géo-localisation, alors on pourra attendre longtemps la fin de cet appel.

Concrètement, un appel à getLastLocation permet de récupérer une référence vers une tâche de recherche de la localisation. Il faut ensuite, associer un écouteur de réussite de tâche pour récupérer sont résultat :

Task<Location> t = locationClient.getLastLocation();
t.addOnSuccessListener(MainActivity.this, new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location loc) {
Toast.makeText(MainActivity.this, "Vous étiez récemment ici : "+loc.getLatitude()+" / "+loc.getLongitude(), Toast.LENGTH_LONG).show();
}
});
t.addOnFailureListener(MainActivity.this, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Toast.makeText(MainActivity.this,"Désolé, la localisation n'a pas abouti", Toast.LENGTH_LONG).show();
}
});

Pour être notifié des différentes positions successives avec l'API Google Play Services, il est nécessaire de fournir deux éléments au système : un descripteur de requête et un écouteur. Le descripteur de requête contient l'ensemble des paramètres de l'enregistrement. Par exemple, la fréquence désirée pour le rafraichissement, la distance minimum entre 2 points et le niveau de précision voulu.

LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(10000); // En millisecondes
locationRequest.setFastestInterval(5000); // En millisecondes
locationRequest.setSmallestDisplacement(50); // en mètres
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

Les valeurs de priorité possibles sont :

  • PRIORITY_HIGH_ACCURACY  : Niveau de précision maximum.

  • PRIORITY_BALANCED_POWER_ACCURACY  : C'est une précision gros-grain (environ 100 mètres) qui consommera peu de batterie. Le fournisseur matériel de service utilisé (GPS, Wifi, GSM) dépendra de la précision du réseau. Si elle est suffisante, alors c'est elle qui sera utilisée.

  • PRIORITY_LOW_POWER  :  précision très gros grain (10Km) qui consommera très peu d'énergie.

  • PRIORITY_NO_POWER  : niveau de consommation d'énergie "nulle". Vous ne serez notifié que si une information est disponible (demandée par une autres application).

L'écouteur, quant à lui, doit hériter de la classe abstraite LocationCallback  et fournir au moins la méthode onLocationResult :

GPSLocationCallback= new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
Toast.makeText(
MainActivity.this,
"Vous bougez, mois vous êtes ici : "+loc.getLatitude()+" / "+loc.getLongitude()+ " (Nb locs : "+locationResult.getLocations().size()+")",
Toast.LENGTH_LONG).show();
}
};

Les données reçues par cette méthode sont un ensemble de localisations.
Il suffit ensuite d'enregistrer l'écouteur auprès du système avec les paramètres définis :

GPSLocationClient.requestLocationUpdates(
locationRequest,
GPSLocationCallback,
null /* Looper */);

Là aussi, il ne faut pas oublier de dé-senregistrer son application du système lors de la mise en pause de l'activité :

protected void onPause() {
super.onPause();
if(GPSLocationClient!=null ) {
if(GPSLocationCallback!=null) {
GPSLocationClient.removeLocationUpdates(GPSLocationCallback);
}
GPSLocationCallback=null;
}
}

En remettant bout à bout toutes les actions nécessaires pour enregistrer une routine de localisation auprès du Google Play Services, le code devient :

private final static int REQUEST_CODE_AUPDATE_LOCATION=42;
private FusedLocationProviderClient GPSLocationClient;
private LocationCallback GPSLocationCallback;
public void GPSUpdateLocation(){
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_CODE_UPDATE_LOCATION);
} else {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(10000);
locationRequest.setFastestInterval(5000); // en millisecondes
locationRequest.setSmallestDisplacement(50); // en mètres
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
GPSLocationCallback= new LocationCallback() {
@Override
public void onLocationResult(LocationResult loc) {
Toast.makeText(
MainActivity.this,
"Vous bougez, mois vous êtes ici: "+loc.getLatitude()+" / "+loc.getLongitude()+" (Nb locs : "+locationResult.getLocations().size()+")",
Toast.LENGTH_LONG).show();
}
};
GPSLocationClient.requestLocationUpdates(
locationRequest,
GPSLocationCallback,
null /* Looper */);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_UPDATE_LOCATION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
GPSUpdateLocation();
} else {
Toast.makeText(MainActivity.this, "Permission refusée.", Toast.LENGTH_LONG).show();
}
return;
}
}
@Override
protected void onPause() {
super.onPause();
if(GPSLocationClient!=null ) {
if(GPSLocationCallback!=null) {
GPSLocationClient.removeLocationUpdates(GPSLocationCallback);
}
GPSLocationCallback=null;
GPSLocationClient=null;
}
}

Exemple de certificat de réussite
Exemple de certificat de réussite