Ejercicio Intermedio D: Localización por GPS
A. Descripción
En este ejercicio, introduciremos cómo funcionan los servicios de localización en Android y explicaremos cómo conseguir que la API deAndroid nos indique nuestraposición GPS. Para ello crearemos una aplicación muy sencilla que activa y desactiva el receptor GPS del dispositivo Android, devolviendo la posición al usuario.
En Android, tenemos dos paquetes de servicios relacionados con la geolocalización, el mapa y la posición. Veremos cómo obtener nuestra posición de forma sencilla y rápida.
Hay dos posibles fuentes de información sobre nuestra posición, el receptor GPS del dispositivo o la red, y por red nos referimos a la red que esté utilizando el dispositivo para conectarse a internet. Así, el dispositivo nos puede devolver nuestra posición en base a qué antenas de telefonía móvil está utilizando, triangulando la posición, o si estamos utilizando un hotspotWi-Fi cuya posición es conocida, obtenerla. En cualquier caso, cada método tiene sus ventajas y sus desventajas, y aunque en un principio el GPS siempre parece el mejor, no suele funcionar bien en interiores, y un uso excesivo drena mucho la batería, cosa que como programadores debemos siempre tener en cuenta, y asegurarnos de que nuestras aplicaciones cuiden de la batería lo máximo posible.
B. Implementación
B-1. Interfaz
Para empezar, debemos crear un nuevo proyecto NOTA: MAPS GOOGLE (al final del enunciado) y añadirle al Layout dos nuevos TextView, uno para mostrar la longitud y otro para la latitud. Justo debajo, añadiremos un ToggleButton, que es un botón con una peculiaridad: posee dos estados. Puede estar activado (el método isChecked() retorna true) o desactivado (isChecked() retorna false). Lo utilizaremos para activar y desactivar el GPS. Como el ToggleButton tiene dos estados, puede llevar dos etiquetas distintas, una para cada estado. El atributo para la etiqueta cuando está desactivado es
textOff y para cuando está activado textOn. Naturalmente, a los dos TextView y al
ToggleButton debemos asignarles una ID.Ahora pasamos a la actividad. Empezamos añadiendo un método privado llamado
initConfig() al que llamaremos en el constructor. Luego añadimos tres variables privadas
a la actividad, dos TextView y un ToggleButton, y añadimos el código necesario en
initConfig() para que estas variables hagan referencia a las vistas del main.xml.
B-2. La API de Localización
Antes de poder escribir código en el que solicitamos información al sistema operativo
de nuestra posición (lo cual requiere activar las funciones de GPS), necesitamos que nuestra aplicación tenga los permisos necesarios, o Android nos cerrará la aplicación. Esto lo hacemos acudiendo al manifest, y añadiendo estas dos líneas al final, justo antes de que se cierre el tagmanifest:
<usespermissionandroid:name="android.permission.ACCESS_FINE_LOCATION"></use s-permission>
<usespermissionandroid:name="android.permission.ACCESS_COARSE_LOCATION"></u ses-permission>
Estos permisos nos permiten obtener información directa del chip GPS
(ACCESS_FINE_LOCATION) o de la red (ACCESS_COARSE_LOCATION ). A partir de ahora todo
el código es Java, así que volvemos a nuestra única actividad, y le añadimos éstas variables:
privateLocationManagerlocMgr;
privateLocationListeneronLocationChange;
La primera variable, de tipo LocationManager, es la que nos permite tener acceso
a objetos de la clase Location, que son los que nos darán las coordenadas de longitud y latitud que queremos. Como sabemos, la API de Android se basa mucho en el patrón de diseño Observador, y esto también se extiende a la API de localización. Lo que queremos mostrar en nuestra aplicación es siempre la última posición que obtiene el dispositivo a través del receptor, por lo que tenemos que registrarnos como observador, y eso lo hacemos a través del
LocationListener.
Volvamos al initConfig(). Tras inicializar los TextView y el ToggleButton, inicializaremos el LocationManager y el LocationListener, con este código:
locMgr = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
onLocationChange = newLocationListener() {
publicvoidonLocationChanged(android.location.Location location) { longitutdeTextView.setText("Longitud: " + location.getLongitude()); latitudeTextView.setText("Latitud: " + location.getLatitude()); Log.i(LOG_TAG, "Received Location update.");
}
publicvoidonProviderDisabled(String provider) { Log.i(LOG_TAG, "Location provider " + provider + " has been disabled.");
}
publicvoidonProviderEnabled(String provider) {
Log.i(LOG_TAG, "Location provider " + provider + " has been enabled.");
}
publicvoidonStatusChanged(String provider, int status, Bundle extras) {
Log.i(LOG_TAG, "Location provider " + provider + " has changed status
to " + status);
}
};
NOTA: Suponemos que el alumno ya está familiarizado con el uso del LogCat y de sus funciones.
No podemos crear una nueva instancia de LocationManager, sino que debemos pedírselo al sistema, y eso lo hacemos mediante la llamada getSystemService(). Luego inicializamos el LocationListener, que actuará cuando lo registremos. La llamada onLocationChanged() se produce cuando el dispositivo consigue un nuevo dato sobre nuestra posición, el onProviderDisabled() es llamado cuando pedimos que Android nos
dé información de localización de un proveedor que está deshabilitado por el usuario (por
ejemplo, el GPS está desactivado), y lo contrario en el caso del onProviderEnabled(). El último método, onStatusChanged(), es llamado cuando hay cambios respecto al proveedor de servicio de localización, por ejemplo, al volverse no disponible, al volver a estar disponible, etc. A veces también provee de información extra en el Bundle, como el número de satélites utilizados para obtener la posición GPS.
Podemos ver que, en el método que más nos interesa, el onLocationChanged(), actualizamos el texto que se muestra en los TextView con la localización de la que nos
provee Android. El objeto Location provee de más información, y animamos a los alumnos a indagar un poco y a descubrir qué más nos puede ofrecer.
Ahora tenemos el segmento de código más importante, el del ToggleButton. Lo que queremos es que cuando el usuario active el botón, registremos nuestro LocationListener con el proveedor del servicio de localización por GPS para que nos avise en cuanto tenga una nueva posición (es decir, que llame a los métodos correspondientes
de onLocationChange), y que cuando desactivemos el botón, dejemos de solicitar posiciones por GPS para ahorrar batería. Si nosotros éramos los únicos solicitando información
GPS, Android desactivará el chip automáticamente al decirle que ya no queremos más información sobre nuestra posición.
Dado que todos somos humanos, tenemos que programar para el peor caso. ¿Qué ocurre si el usuario pulsa el botón Atrás sin haber vuelto a tocar el ToggleButton para decirle a Android que deje de solicitar información por GPS?Android mantendrá el chip GPS activo pensando que hay una aplicación solicitando información, y dado que aún estamos registrados en el listener, Android no podrá liberar la memoria de nuestra aplicación, drenando la batería hasta que logremos matar el proceso o apaguemos el teléfono. Debemos
ser muy precavidos con esto. Éste es el código del ToggleButton, que ponemos al final del
initConfig():
toggleButton.setOnClickListener(newView.OnClickListener() {
@Override
publicvoidonClick(View v) {
if (toggleButton.isChecked()){
locMgr.requestLocationUpdates(LocationManager.GPS_PROVIDER,
0, 0f, onLocationChange);
}
else {
});
NOTA: A partir de este código, los alumnos deben ser capaces de añadir a la actividad el método onDestroy() con las llamadas necesarias, la de la clase padre y la que informa al sistema operativo que ya no queremos más información de localización.
En el onClick(), estamos registrando nuestro LocationListener (onLocationChange) para recibir información sobre nuestra posición GPS. En vez de utilizar el GPS, también podríamos haber utilizado la red (de telefonía o Wi-Fi, eso lo decide Android) cambiando el primer argumento porLocationManager.NETWORK_PROVIDER. En el
último argumento le decimos cuál es nuestro LocationListener asociado a la petición, y luego quedan el segundo y el tercer argumento, los relacionados directamente con el gasto de la batería. El segundo argumento es el tiempo mínimo, en milisegundos, que debe pasar antes
de que Android vuelva a solicitar nuestra posición GPS. Por ejemplo, un valor de 10000 significaría que como poco, pasarán diez segundos entre petición y petición. El tercer argumento es la distancia mínima, en metros, entre petición y petición. Nosotros hemos solicitado que Android nos diga nuestra posición lo antes posible, sin descanso, prácticamente
en tiempo real, lo cual es la configuración que más consume la batería. No se recomienda realizar peticiones con intervalo inferior a 1 minuto en aplicaciones de uso diario. Por supuesto, hay excepciones; por ejemplo, en un navegador GPS para el coche, se presupone que el usuario comprará el adaptador para darle corriente al dispositivo Android, ya que lo que prima en este caso de uso es la fiabilidad de la información al conducir y no el consumo energético.
Por último, queremos mostrar un dato al usuario para empezar, el de la última posición conocida por el sistema, sea de GPS o de la red. Esto lo haremos con el siguiente código, que ya cierra el método initConfig():
android.location.Location location = locMgr.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (location == null)
location =
locMgr.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
if (location != null) {
longitutdeTextView.setText("Longitud: " + location.getLongitude());
latitudeTextView.setText("Latitud: " + location.getLatitude());
}
else {
longitutdeTextView.setText("Longitud: No disponible");
latitudeTextView.setText("Latitud: No disponible");
}
Como vemos, lo que hacemos es pedirle a Android cuál es la última posición que conoce a través del GPS, y si no conoce ninguna (devolvería null), le preguntamos por el proveedor de red, y si aún así no tenemos una posición, mostramos que no tenemos la información disponible. Podemos intentar darle las posiciones al emulador con el DDMS, sin embargo, lo más divertido es instalar esta aplicación en un dispositivo Android y caminar, viendo cómo van cambiando los datos.
C. Conclusión
La geo-localización es una de las cualidades más solicitadas de todo smartphone, y sobre todo, de cualquier plataforma móvil. Es un recurso que podemos aplicar prácticamente
en todos los aspectos de diseño en nuestras aplicaciones, y lo más importante, aún está pendiente de ser descubierto por la inmensa mayoría de usuarios con teléfonos móviles.
En este ejercicio hemos introducido la API de localización, pero hay muchísimas cosas relacionadas con el entorno de un dispositivo Android que los alumnos están todavía por descubrir, como el acelerómetro, la brújula digital, etc.
D. Notas Google Map
1. Al crear el proyecto debemos indicarle no la version 1.5, sino la de Google (para que incluya las librerías de los mapas).
2. Debemos crear un nuevo emulador… indicándole lo mismo.
3. Unavez que ya tengamos realizado todo el ejercicio anterior, debemos obtener la clave para poder utilizarlos mapas de Google:
>keytool -list -keystore “C:\Documents and
Settings\CursoPude\.android\debug.keystore”
con password “android” o directamente:
>keytool -list -keystore “C:\Documents and
Settings\CursoPude\.android\debug.keystore” -storepass android - keypass android
Insertamos el resultado en la siguiente página para obtener la clave:
4. Nos descargamos la imagen http://developer.android.com/resources/tutorials/views/images/androidmarker.png y la
colocamos en la carpeta res/drawable/.
5. Añadimos al AndroidManifes.xml :
a. <uses-library android:name="com.google.android.maps"/>
b. <uses-permission android:name="android.permission.INTERNET" />
6. Creamos un nuevo fichero de layout,con el siguiente contenido:
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.maps.MapView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/mapview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:clickable="true"
android:apiKey="Your Maps API Key goes here"
/>
Incluyendo la clave obtenida anteriormente sustituyendo al texto en negrita.
7. Creamos una nueva actividad que herede de la clase MapActivity. En el onCreate() de esta actividad hacemos el setContentView(), del fichero anterior.
8. Sobreescribimos el metodo siguiente (es obligado).
@Override
protected boolean isRouteDisplayed() {
return false;
}En el onCreate obtenemos el layout creado (acuerdense “findView…..”), e indicarle que nos muestre los controles del mapa:
setBuiltInZoomControls(true)
9. Vamos ahora a añadirle algo ….. los valores de las coordenadas las obtendremos en el onCreate(), mediante el getIntent().getExtra() (ahí está el bundle y obtenemos las coordenadas).
10. Creamos una nueva clase (MiIconito) que implemente ItemizedOverlay. En esa
clase nos creamos un ArrayList de OverlayItems
private ArrayList<OverlayItem> mOverlays = new
ArrayList<OverlayItem>();
11. Le incluimos los siguientes métodos:
// Añade un item
public void addOverlay(OverlayItem overlay) {
mOverlays.add(overlay);
populate();
}
@Override
protected OverlayItem createItem(int i) {
return mOverlays.get(i);
}
@Override
public int size() {
return mOverlays.size();
}
// Este será nuestro contructor
public MiIconito
(Drawable defaultMarker, Context context) {
super(defaultMarker);
mContext = context;
}
@Override
protected boolean onTap(int index) { OverlayItem item = mOverlays.get(index);
AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
dialog.setTitle(item.getTitle());
dialog.setMessage(item.getSnippet())
dialog.show();
return true;
}
Ahora en el onCreate debemos añadirle:
List<Overlay> mapOverlays = mapView.getOverlays(); Drawable drawable =
this.getResources().getDrawable(R.drawable.androidmarker);
MiIconito itemizedoverlay = new MiIconito(drawable, Actividad.this);
Las coordenadas obtenidas mediante el bundle (serán 2 enteros -> getInt(“clave”)), y nos
creamos un GeoPoint(), pasándole esos 2 enteros.
GeoPoint point
OverlayItem overlayitem = new OverlayItem(point, "Hola, Mundo!", "Estoy aquí!!");
Y mostramos el punto mediante:
itemizedoverlay.addOverlay(overlayitem);
mapOverlays.add(itemizedoverlay);
12. En la actividad principal, debemos añadir un nuevo botón, el cual será el encargado de pasarle las coordenadas a la actividad de Mapa. Al realizar la llamada deberemos pasarle la lonitud y la latitud, pero como int, para ello multiplicar por 1E6 y convertir a entero “(int)”.