I Location Services permettono al programmatore di creare applicazioni location-aware, ovvero consapevoli della posizione geografica in
cui si trova il dispositivo in un dato istante.
Al giorno d'oggi è facile immaginare l'utilità di questa funzionalità. Il suo vantaggio principale consiste nell'associare coordinate terrestri –
latitudine e longitudine – ai dati che regolarmente raccogliamo e consultiamo con i dispositivi Android. Gli scopi possono essere i più disparati, dalle
finalità sportive a quelle commerciali, dalla consultazione di carte stradali ad una più completa definizione dei dati immagazzinati.
Nella guida allo sviluppo di app Android è stata presentata la modalità “classica” di utilizzare la
localizzazione in un'applicazione. Le funzionalità qui descritte permettono un approccio di più alto livello, ottimizzato, in grado di liberare il
programmatore dalla gestione specifica dei provider, nonché di rendere meno dispendioso il consumo della batteria. Tali vantaggi sono dovuti in buona parte
dal sostegno offerto dai Google Play Services.
Prerequisiti di “programmazione Google”
Dei concetti incontrati finora in questa guida, un paio necessitano di essere rimarcati, in quanto costituiscono la base per l'uso dei Location Services:
-
i Google Play Services anche in questo caso offrono le funzionalità necessarie. Il progetto deve essere collegato alla libreria, come
già spiegato in precedenza in questa guida; -
la classe GoogleApiClient fornisce un accesso uniforme a qualunque set di API offerte da Google. L'approccio ad un servizio Google sarà
quindi estremamente agevole: avremo una fase di connessione (metodoconnect
), seguita dalla vera interazione con il servizio, ed infine una fase di
disconnessione (metododisconnect
).
Connessione e disconnessione saranno propriamente inserite nel ciclo di vita del componente Android che utilizzeremo, un'Activity nel caso dell'esempio.
La localizzazione
Per realizzare un’applicazione location-aware è importante considerare le permission necessarie. Sarà necessario fornire quelle necessarie
sia per attivare la localizzazione che per indicarne la tecnologia di attuazione.
Esistono due categorie principali di geolocalizzazione:
-
Network-based: basata sulle informazioni offerte dalle reti mobili. Meno precisa ma disponibile praticamente su tutti i dispositivi
Android; - GPS: sistema basato su una rete di satelliti, di qualità di gran lunga superiore alla precedente.
Le permission che possono essere inserite nel file manifest sono:
- ACCESS_COARSE_LOCATION: per la localizzazione network-based;
- ACCESS_FINE_LOCATION: per l'uso del GPS.
Le operazioni relative alla localizzazione verranno svolte in maniera tendenzialmente simile alle API tradizionali.
Oltre alle fasi di connessione/disconnessione dell'oggetto GoogleApiClient, ci si dovrà occupare per lo più di:
- richiedere aggiornamenti periodici sulla posizione del dispositivo;
- gestire le informazioni di localizzazione ricevute da parte del servizio;
- annullare la richiesta di aggiornamento della posizione da parte del sistema, tutte le volte in cui ci si voglia disconnettere.
Le classi coinvolte nelle tre fasi sono:
-
LocationServices: è l'entry point del sistema di localizzazione dei Google Play Services. Al suo interno useremo, in particolare, il
membro API in fase di configurazione di GoogleApiClient, e l'oggetto FusedLocationApi, che offre il riferimento
al provider di localizzazione da utilizzare.
A tale scopo, FusedLocationApi offre due metodi basilari:requestLocationUpdates
eremoveLocationUpdates
i quali, rispettivamente, permettono di effettuare
ed annullare l’iscrizione al sistema di notifiche del servizio di localizzazione; -
LocationRequest: è la classe che permette di specificare le modalità con cui desideriamo ricevere notifiche da parte del servizio di
localizzazione. Tramite i metodi “setter” che offre, permette di indicare l'intervallo di tempo minimo in millisecondi che dovrà intercorrere tra una
notifica e l'altra (setInterval
), la distanza minima percorsa in metri (setDisplacement
) e l'accuratezza della misurazione della localizzazione
(setPriority
); -
LocationListener: è un'interfaccia che dovrà essere implementata dall'Activity affinchè essa possa svolgere il ruolo di
ricevitore delle notifiche di localizzazione. Sarà necessario l'override del metodoonLocationChanged
, il quale riceverà un oggetto di classe Location, approfondita meglio nel seguito.
Lo scopo del metodoonLocationChanged
è quello di utilizzare le informazioni di localizzazione appena notificate. Tipicamente esse verranno sfruttate per
l'aggiornamento della interfaccia utente, immagazzinate in un database o magari inviate in rete; -
Location: rappresenta il mattone fondamentale delle informazioni di localizzazione. Al suo interno contiene le coordinate terrestri
(latitudine e longitudine) ma anche molti altri dati come altitudine, orientamento, velocità, etc. Tutte queste informazioni saranno leggibili attraverso
appositi metodi “getter”.
Da notare, infine, che delle quattro classi poc'anzi menzionate (LocationServices, LocationRequest, LocationListener e Location) le prime tre appartengono
al package com.google.android.gms.location, che ne rivela l'appartenenza alle API offerte da Google, mentre l'ultima, Location,
appartiene al package android.location; si tratta quindi della stessa classe che viene utilizzata comunemente con le API “classiche” di
localizzazione.
L'esempio
Le informazioni di volta in volta fornite dai Location Services verranno mostrate in un layout molto semplice, che specificheremo nel file /res/layout/activity_main.xml:
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TableRow android:padding="5dp">
<TextView android:text="Data ora" android:padding="5dp"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<TextView android:id="@+id/timestamp" android:padding="5dp"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</TableRow>
<TableRow android:padding="5dp">
<TextView android:text="Latitudine" android:padding="5dp"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<TextView android:id="@+id/latitude" android:padding="5dp"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</TableRow>
<TableRow android:padding="5dp">
<TextView android:text="Longitudine" android:padding="5dp"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<TextView android:id="@+id/longitude" android:padding="5dp"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</TableRow>
<TableRow android:padding="5dp">
<TextView android:text="Località" android:padding="5dp"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<TextView android:id="@+id/where" android:padding="5dp"
android:lines="2"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</TableRow>
</TableLayout>
All'interno del file manifest non dovremo inoltre dimenticare la permission necessaria:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
Il codice dell'Activity sarà invece strutturato come mostrato di seguito.
public class MainActivity extends Activity implements OnConnectionFailedListener, ConnectionCallbacks,LocationListener
{
private static final int REQUEST_RESOLVE_ERROR = 0;
private static final String DIALOG_ERROR = null;
private GoogleApiClient googleApiClient;
private boolean resolvingError;
private LocationRequest mLocationRequest;
…
…
}
Come si può vedere, l’Activity dovrà implementare tre interfacce principali: due relative alla connessione al servizio ( OnConnectionFailedListener e ConnectionCallbacks) e una - il LocationListener - per ricevere le informazioni di localizzazione.
I membri definiti all'interno dell'Activity sono per lo più quelli già incontrati a proposito delle operazioni di connessione e disconnessione ai
servizi Google.
I primi metodi che inseriremo nell'Activity si occuperanno di istanziare un oggetto GoogleApiClient oltre che di avviare la
connessione (nel metodo onStart
) e di disconnetterla (metodo onStop
):
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
googleApiClient = new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
@Override
protected void onStart() {
super.onStart();
if (!resolvingError)
{
googleApiClient.connect();
}
}
@Override
protected void onStop()
{
if (googleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, this);
googleApiClient.disconnect();
}
super.onStop();
}
Una volta “assolti gli obblighi” di connessione e disconnessione dai Google Play Services, è il momento di avviare la ricezione di informazioni di
localizzazione da parte del provider. La richiesta, formulata come oggetto LocationRequest, viene inoltrata all'interno del metodo onConnected
.
Solo quest'ultimo infatti ci consente di sapere con certezza che la connessione ai Google Play Services è attiva e, pertanto, che il servizio è
utilizzabile.
Gli altri metodi qui presentati serviranno per lo più alla gestione delle problematiche di connessione al servizio Google.
@Override
public void onConnected(Bundle arg0)
{
mLocationRequest = LocationRequest.create();
mLocationRequest.setInterval(10000);
mLocationRequest.setSmallestDisplacement(5);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient,mLocationRequest,this);
}
@Override
public void onConnectionFailed(ConnectionResult result)
{
if (resolvingError)
return;
if (result.hasResolution()) {
try {
resolvingError = true;
result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
}
catch (SendIntentException e)
{
googleApiClient.connect();
}
} else {
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),this, REQUEST_RESOLVE_ERROR).show();
resolvingError = true;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_RESOLVE_ERROR) {
resolvingError = false;
if (resultCode == RESULT_OK)
{
if (!googleApiClient.isConnecting() &&
!googleApiClient.isConnected()) {
googleApiClient.connect();
}
}
}
}
@Override
public void onConnectionSuspended(int arg0)
{
/*
Connessione sospesa
* */
}
Infine, gli ultimi metodi da inserire nell'Activity riguarderanno esclusivamente la gestione delle informazioni di localizzazione.
Primo tra tutti onLocationChanged, richiesto dall'implementazione dell'interfaccia LocationListener:
@Override
public void onLocationChanged(final Location location)
{
Date timestamp = new Date(location.getTime());
updateText(R.id.timestamp, timestamp.toString());
double latitude = location.getLatitude();
updateText(R.id.latitude, String.valueOf(latitude));
double longitude = location.getLongitude();
updateText(R.id.longitude, String.valueOf(longitude));
new AsyncTask()
{
@Override
protected String doInBackground(Void... voids)
{
Geocoder coder=new Geocoder(MainActivity.this);
try {
List l=coder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);
if (l.size()>0)
return l.get(0).getAddressLine(0);
} catch (IOException e) {
return null;
}
return null;
}
@Override
protected void onPostExecute(String result) {
if (result!=null)
updateText(R.id.where, result);
else
updateText(R.id.where, "N.A.");
}
}.execute();
}
Al suo interno abbiamo utilizzato un oggetto Location, che contiene le ultime informazioni di posizione rilevate. I dati in esso inclusi
vengono usati per aggiornare l'interfaccia utente; a tale scopo, si è creato un altro piccolo metodo di utilità per gestire l'inserimento delle stringhe in
una TextView:
private void updateText(int id, String text)
{
TextView textView = (TextView) findViewById(id);
textView.setText(text);
}
Un aspetto da notare è che in onLocationChanged
si è provveduto a convertire le coordinate geografiche in un indirizzo stradale. Questa operazione, come il
suo inverso (da informazioni stradali a coordinate geografiche), rientra nella pratica denominata Geocoding, e viene attuata mediante un
oggetto di classe Geocoder. Essenzialmente il suo utilizzo si basa sull'invocazione del metodo getFromLocation
, passandogli i dati relativi a latitudine e longitudine.
Gli indirizzi restituiti saranno collocati in una lista di oggetti Address. Questo semplice procedimento è stato attuato
all'interno di un metodo doInBackground
di un AsyncTask per evitare che i tempi di latenza della richiesta bloccassero l'interattività l'Activity.