miércoles, 3 de diciembre de 2014

Localización Geográfica en Android



Localización Geográfica Básica

La localización geográfica en Android es uno de esos servicios que, a pesar de requerir poco código para ponerlos en marcha, no son para nada intuitivos ni fáciles de llegar a comprender por completo. Y esto no es debido al diseño de la plataforma Android en sí, sino a la propia naturaleza de este tipo de servicios. Por un lado, existen multitud de formas de obtener la localización de un dispositivo móvil, aunque la más conocida y popular es la localización por GPS, también es posible obtener la posición de un dispositivo por ejemplo a través de las antenas de telefonía móvil o mediante puntos de acceso Wi-Fi cercanos, y todos cada uno de estos mecanismos tiene una precisión, velocidad y consumo de recursos distinto. Por otro lado, el modo de funcionamiento de cada uno de estos mecanismos hace que su utilización desde nuestro código no sea todo lo directa e intuitiva que se desearía. Iremos comentando todo esto a lo largo del apartado, pero vayamos paso a paso.

¿Qué mecanismos de localización tenemos disponibles?

Lo primero que debe conocer una aplicación que necesite obtener la localización geográfica es qué mecanismos de localización (proveedores de localización, o location providers) tiene disponibles en el dispositivo. Como ya hemos comentado, los más comunes serán el GPS y la localización mediante la red de telefonía, pero podrían existir otros según el tipo de dispositivo.

La forma más sencilla de saber los proveedores disponibles en el dispositivo es mediante una llamada al método getAllProviders() de la clase LocationManager, clase principal en la que nos basaremos siempre a la hora de utilizar la API de localización de Android. Para ello, obtendremos una referencia al location manager llamando a getSystemService(LOCATION_SERVICE), y posteriormente obtendremos la lista de proveedores mediante el método citado para obtener la lista de nombres de los proveedores:


Una vez obtenida la lista completa de proveedores disponibles podríamos acceder a las propiedades de cualquiera de ellos (precisión, coste, consumo de recursos, o si es capaz de obtener la altitud, la velocidad, …). Así, podemos obtener una referencia al provider mediante su nombre llamando al método getProvider(nombre) y posteriormente utilizar los métodos disponibles para conocer sus propiedades, por ejemplo getAccuracy() para saber su precisión (tenemos disponibles las constantes Criteria. ACCURACY_FINE para precisión alta, y Criteria.ACCURACY_COARSE para precisión media), supportsAltitude() para saber si obtiene la altitud, o getPowerRequirement() para obtener el nivel de consumo de recursos del proveedor. La lista completa de métodos para obtener las características de un proveedor se puede consultar en la documentación oficial de la clase LocationProvider.


Al margen de esto, hay que tener en cuenta que la lista de proveedores devuelta por el método getAllProviders() contendrá todos los proveedores de localización conocidos por el dispositivo, incluso si éstos no están permitidos (según los permisos de la aplicación) o no están activados, por lo que esta información puede que no nos sea de mucha ayuda.

¿Qué proveedor de localización es mejor para mi aplicación?

Android proporciona un mecanismo alternativo para obtener los proveedores que cumplen unos determinados requisitos entre todos los disponibles. Para ello nos permite definir un criterio de búsqueda, mediante un objeto de tipo Criteria, en el que podremos indicar las características mínimas del proveedor que necesitamos utilizar (podéis consultar la documentación oficial de la clase Criteria para saber todas las características que podemos definir). Así, por ejemplo, para buscar uno con precisión alta y que nos proporcione la altitud definiríamos el siguiente criterio de búsqueda:


Tras esto, podremos utilizar los métodos getProviders() o getBestProvider() para obtener la lista de proveedores que se ajustan mejor al criterio definido o el proveedor que mejor se ajusta a dicho criterio, respectivamente. Además, ambos métodos reciben un segundo parámetro que indica si queremos que sólo nos devuelvan proveedores que están activados actualmente. Veamos cómo se utilizarían estos métodos:


Con esto, ya tenemos una forma de seleccionar en cada dispositivo aquel proveedor que mejor se ajusta a nuestras necesidades.

¿Está disponible y activado un proveedor determinado?

Aunque, como ya hemos visto, tenemos la posibilidad de buscar dinámicamente proveedores de localización según un determinado criterio de búsqueda, es bastante común que nuestra aplicación esté diseñada para utilizar uno en concreto, por ejemplo el GPS, y por tanto necesitaremos algún mecanismo para saber si éste está activado o no en el dispositivo. Para esta tarea, la clase LocationManager nos proporciona otro método llamado isProviderEnabled() que nos permite hacer exactamente lo que necesitamos. Para ello, debemos pasarle el nombre del provider que queremos consultar. Para los más comunes tenemos varias constantes ya definidas:

* LocationManager.NETWORK_PROVIDER. Localización por la red de telefonía.
* LocationManager.GPS_PROVIDER. Localización por GPS.

De esta forma, si quisiéramos saber si el GPS está habilitado o no en el dispositivo (y actuar en consecuencia), haríamos algo parecido a lo siguiente:


En el código anterior, verificamos si el GPS está activado y en caso negativo mostramos al usuario un mensaje de advertencia. Este mensaje podríamos mostrarlo sencillamente en forma de notificación de tipo toast, pero en el próximo apartado sobre localización veremos cómo podemos, además de informar de que el GPS está desactivado, invitar al usuario a activarlo dirigiéndolo automáticamente a la pantalla de configuración del dispositivo.

El GPS ya está activado, ¿y ahora qué?

Una vez que sabemos que nuestro proveedor de localización favorito está activado, ya estamos en disposición de intentar obtener nuestra localización actual. Y aquí es donde las cosas empiezan a ser menos intuitivas. Para empezar, en Android no existe ningún método del tipo "obtenerPosiciónActual()". Obtener la posición a través de un dispositivo de localización como por ejemplo el GPS no es una tarea inmediata, sino que puede requerir de un cierto tiempo de procesamiento y de espera, por lo que no tendría sentido proporcionar un método de ese tipo.

Si buscamos entre los métodos disponibles en la clase LocationManager, lo más parecido que encontramos es un método llamado getLastKnownLocation(String provider), que como se puede suponer por su nombre, nos devuelve la última posición conocida del dispositivo devuelta por un provider determinado. Es importante entender esto: este método NO devuelve la posición actual, este método NO solicita una nueva posición al proveedor de localización, este método se limita a devolver la última posición que se obtuvo a través del proveedor que se le indique como parámetro. Y esta posición se pudo obtener hace pocos segundos, hace días, hace meses, o incluso nunca (si el dispositivo ha estado apagado, si nunca se ha activado el GPS, …). Por tanto, cuidado cuando se haga uso de la posición devuelta por el método getLastKnownLocation().

Entonces, ¿de qué forma podemos obtener la posición real actualizada? Pues la forma correcta de proceder va a consistir en algo así como activar el proveedor de localización y suscribirnos a sus notificaciones de cambio de posición. O dicho de otra forma, vamos a suscribirnos al evento que se lanza cada vez que un proveedor recibe nuevos datos sobre la localización actual. Y para ello, vamos a darle previamente unas indicaciones (que no ordenes, ya veremos esto en el próximo apartado) sobre cada cuanto tiempo o cada cuanta distacia recorrida necesitaríamos tener una actualización de la posición.

Todo esto lo vamos a realizar mediante una llamada al método requestLocationUpdates(), al que deberemos pasar 4 parámetros distintos:

* Nombre del proveedor de localización al que nos queremos suscribir.
* Tiempo mínimo entre actualizaciones, en milisegundos.
* Distancia mínima entre actualizaciones, en metros.
* Instancia de un objeto LocationListener, que tendremos que implementar previamente para
definir las acciones a realizar al recibir cada nueva actualización de la posición.

Tanto el tiempo como la distancia entre actualizaciones pueden pasarse con valor 0, lo que indicaría que ese criterio no se tendrá en cuenta a la hora de decidir la frecuencia de actualizaciones. Si ambos valores van a cero, las actualizaciones de posición se recibirán tan pronto y tan frecuentemente como estén disponibles.
Además, como ya hemos indicado, es importante comprender que tanto el tiempo como la distancia especificadas se entenderán como simples indicaciones o "pistas" para el proveedor (al menos para versiones no recientes de Android), por lo que puede que no se cumplan de forma estricta. En el próximo apartaapartado intentaremos ver esto con más detalle para entenderlo mejor. Por ahora nos basta con esta información.

En cuanto al listener, éste será del tipo LocationListener y contendrá una serie de métodos asociados a los distintos eventos que podemos recibir del proveedor:

* onLocationChanged(location). Lanzado cada vez que se recibe una actualización de la
posición.
* onProviderDisabled(provider). Lanzado cuando el proveedor se deshabilita.
* onProviderEnabled(provider). Lanzado cuando el proveedor se habilita.
* onStatusChanged(provider, status, extras). Lanzado cada vez que el proveedor
cambia su estado, que puede variar entre OUT_OF_SERVICE, TEMPORARILY_UNAVAILABLE,
AVAILABLE.

Por nuestra parte, tendremos que implementar cada uno de estos métodos para responder a los eventos del proveedor, sobre todo al más interesante, onLocationChanged(), que se ejecutará cada vez que se recibe una nueva localización desde el proveedor. Veamos un ejemplo de cómo implementar un listener de este tipo:


Como podéis ver, en nuestro caso de ejemplo nos limitamos a mostrar al usuario la información recibida en el evento, bien sea un simple cambio de estado, o una nueva posición, en cuyo caso llamamos al método auxiliar mostrarPosicion() para refrescar todos los datos de la posición en la pantalla. Para este ejemplo hemos construido una interfaz muy sencilla, donde se muestran 3 datos de la posición (latitud, longitud y precisión) y un campo para mostrar el estado del proveedor. Además, se incluyen dos botones para comenzar y detener la recepción de nuevas actualizaciones de la posición. No incluyo aquí el código de la interfaz para no alargar más el apartado, pero puede consultarse en el código fuente suministrado al final del texto. El aspecto de nuestra ventana es el siguiente:


En el método mostrarPosicion() nos vamos a limitar a mostrar los distintos datos de la posición pasada como parámetro en los controles correspondientes de la interfaz, utilizando para ello los métodos proporcionados por la clase Location. En nuestro caso utilizaremos getLatitude(), getAltitude() y getAccuracy() para obtener la latitud, longitud y precisión respectivamente. Por supuesto, hay otros métodos disponibles en esta clase para obtener la altura, orientación, velocidad, etc… que se pueden consultar en la documentación oficial de la clase Location. Veamos el código:


¿Por qué comprobamos si la localización recibida es null? Como ya hemos dicho anteriormente, no tenemos mucho control sobre el momento ni la frecuencia con la que vamos a recibir las actualizaciones de posición desde un proveedor, por lo que tampoco estamos seguros de tenerlas disponibles desde un primer momento. Por este motivo, una técnica bastante común es utilizar la posición que devuelve el método getLastKnownLocation() como posición "provisional" de partida y a partir de ahí esperar a recibir la primera actualización a través del LocationListener. Y como también dijimos, la última posición conocida podría no existir en el dispositivo, de ahí que comprobemos si el valor recibido es null. Para entender mejor esto, a continuación tenéis la estructura completa del método que lanzamos al comenzar la recepción de actualizaciones de posición (al pulsar el botón "Activar" de la interfaz):


Como se puede observar, al comenzar la recepción de posiciones, mostramos en primer lugar la última posición conocida, y posteriormente solicitamos al GPS actualizaciones de posición cada 30 segundos.

Por último, nos quedaría únicamente comentar cómo podemos detener la recepción de nuevas actualizaciones de posición. Algo que es tan sencillo como llamar al método removeUpdates() del location manager. De esta forma, la implementación del botón "Desactivar" sería tan sencilla como esto:


Con esto habríamos concluido nuestra aplicación de ejemplo. Sin embargo, si descargáis el código completo del apartado y ejecutáis la aplicación en el emulador veréis que, a pesar funcionar todo correctamente, sólo recibiréis una lectura de la posición (incluso puede que ninguna). Esto es debido a que la ejecución y prueba de aplicaciones de este tipo en el emulador de Android, al no tratarse de un dispositivo real y no estar disponible un receptor GPS, requiere de una serie de pasos adicionales para simular cambios en la posición del dispositivo.

Todo esto, además de algunas aclaraciones que nos han quedado pendientes en esta primera entrega sobre localización, lo veremos en el próximo apartado. Por ahora os dejo el código fuente completo para que podáis hacer vuestras propias pruebas.



Saludos compañeros, aprovechen la información.








No hay comentarios:

Publicar un comentario en la entrada