En este video te explicamos en detalle todo el proceso para publicar una Aplicación Android…
Proyecto Geolocalización Android
En este ejercicio práctico explicaremos el proceso de geolocalización de un dispositivo Android, mostrando los datos de su ubicación (latitud, longitud, altitud y precisión de la señal) y actualizando de forma constante sus cambios de posición. También podremos visualizar su posición en un mapa de Google, junto con un marcador que mostrará los datos de dirección física del dispositivo.
Puedes descargar todo el código del proyecto al final del tutorial.
En esta aplicación, la información se presentará en dos pantallas que se muestran en las imágenes de abajo. Como particularidad, se podrá observar como la pantalla que muestra las coordenadas recoge los valores a partir de la opción GPS del emulador Genymotion:
Elementos y estructura del proyecto
Vamos a describir brevemente los elementos necesarios para el desarrollo de este proyecto que hemos denominado «Geolocalizacion»:
- Clase SplashScreen, que herede de la clase base Activity, encargada de lanzar una pantalla de presentación al iniciar la aplicación, proporcionando una mayor inmersión del usuario en la aplicación.
- Clase MainActivity, que herede de la clase base Activity, que permitirá mostrar los datos asociados a la localización del dispositivo GPS.
- Clase Mapa, que herede de la clase base FragmentActivity e implemente la interfaz
OnMapReadyCallback
, encargada de mostrar la conversión de los datos de latitud y longitud en una dirección física dentro de un mapa de Google. - Layout
activity_splash_screen.xml
, formado por una componente de tipo ImageView, que mostrará el logotipo de la aplicación. - Layout
activity_main.xml
, formado por cuatro componentes de tipo TextView que mostrarán los datos referentes a las coordenadas de latitud y longitud, además de la altura y precisión de la localización del dispositivo, y un componente de tipo Button, encargado de enviar los datos de las coordenadas obtenidas a la siguiente Activity para la visualización de la posición dentro de un mapa. Además se define un componente de tipo TextView en la parte superior de la pantalla, que indicará si los valores mostrados son valores por defecto o valores proporcionados por el GPS. - Layout
activity_maps.xml
, formado por un fragment, que posibilita la implementación de diferentes layouts en tiempo de ejecución, y que en este caso mostrará el servicio de mapas de Google.
Puedes ver la estructura del proyecto en la siguiente imagen:
Documentación código fuente
Detallamos las diferentes clases, interfaces y métodos que definimos en el proyecto. Asimismo veremos los ficheros de layout y controles que implementan.
Geolocalizacion\app\src\main\java\com\academiaandroid\geolocalizacion\MainActivity.java
- Se define la clase
MainActivity
, que hereda de la clase baseActivity
, y que permite mostrar los datos asociados a la localización del dispositivo GPS:
12public class MainActivity extends Activity { - Posteriormente, se declaran la clase e interfaz encargados de acceder a los datos de posicionamiento del dispositivo. Además dentro del método
onCreate()
se asignan los controles definidos en el layout a cada variable definida:
123456789101112131415161718192021222324[...]private LocationManager locManager;private LocationListener locListener;@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);setContentView(R.layout.activity_main);/*Se asignan los controles definidos en el layout a cada variable definida.*/tvLatitud = (TextView)findViewById(R.id.tvLatitud);tvLongitud = (TextView)findViewById(R.id.tvLongitud);tvPrecision = (TextView)findViewById(R.id.tvPrecision);tvAltura = (TextView)findViewById(R.id.tvAltura);tvPorDefecto = (TextView)findViewById(R.id.tvPorDefecto);btnLocalizar = (Button)findViewById(R.id.btnLocalizar);[...] - El método
rastreoGPS()
se encargará de detectar el cambio de la posición del dispositivo GPS, actualizando los valores mostrados por pantalla:
1234567891011121314151617181920212223242526272829303132333435363738private void rastreoGPS(){/*Se asigna a la clase LocationManager el servicio a nivel de sistema a partir del nombre.*/locManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);/*Se declara y asigna a la clase Location la última posición conocida proporcionada por el proveedor.*/Location loc = locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);mostrarPosicion(loc);//Se define la interfaz LocationListener, que deberá implementarse con los siguientes métodos.locListener = new LocationListener(){//Método que será llamado cuando cambie la localización.@Overridepublic void onLocationChanged(Location location) {mostrarPosicion(location);}//Método que será llamado cuando se produzcan cambios en el estado del proveedor.@Overridepublic void onStatusChanged(String provider, int status, Bundle extras){}//Método que será llamado cuando el proveedor esté habilitado para el usuario.@Overridepublic void onProviderEnabled(String provider){}//Método que será llamado cuando el proveedor esté deshabilitado para el usuario.@Overridepublic void onProviderDisabled(String provider){}}; - Para finalizar dentro de este método, se invocará a
requestLocationUpdates()
, encargado establecer la localización actualizada, recibiendo como parámetros de entrada el nombre del proveedor de localización GPS, el intervalo de tiempo entre cada actualización, distancia en metros entre localizaciones actualizadas, y la variable de tipoLocationListener
que actualizará la localización en caso de producirse nuevos cambios:
1234locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 0, locListener);} - Por último, dentro de esta clase, se define un método que recibe como parámetro de entrada una variable de tipo
Location
, y que permitirá mostrar los diferentes datos de la ubicación geográfica del dispositivo. En el supuesto de no tener habilitada la opción de ubicación, se establecerán valores por defecto (dichos valores se almacenarán en un array de tipo String):
123456789101112131415161718192021222324private String[] mostrarPosicion(Location loc){String[] datos;if(loc != null){tvPorDefecto.setText("(valores GPS)");tvLatitud.setText(String.valueOf(loc.getLatitude()));tvLongitud.setText(String.valueOf(loc.getLongitude()));tvAltura.setText(String.valueOf(loc.getAltitude()));tvPrecision.setText(String.valueOf(loc.getAccuracy()));datos = new String[]{String.valueOf(loc.getLongitude()),String.valueOf(loc.getLatitude())};}else{tvPorDefecto.setText("(valores por defecto)");datos = new String[]{String.valueOf(40.4167754), String.valueOf(-3.7037901999999576),"Posición por defecto"};tvLatitud.setText(String.valueOf(40.4167754));tvLongitud.setText(String.valueOf(-3.7037901999999576));tvAltura.setText(String.valueOf(15.00));tvPrecision.setText(String.valueOf(1.0));}return datos;}
Geolocalizacion\app\src\main\java\com\academiaandroid\geolocalizacion\Mapa.java
- Se define una nueva clase llamada Mapa, que hereda de la clase base FragmentActivity, y que además implementa la interfaz OnMapReadyCallback,
que posibilita la visualización de un mapa con los datos de la localización obtenidos de la Activity principal.
12public class Mapa extends FragmentActivity implements OnMapReadyCallback{ - Dentro del método
onCreate()
, se reciben los datos de las coordenadas obtenidas en la Activity principal, para a continuación llamar al métodomapaDisponible()
:
123456789101112@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);setContentView(R.layout.activity_maps);extra = getIntent().getExtras();longitud = extra.getString("Longitud");latitud = extra.getString("Latitud");mapaDisponible();} - El método
mapaDisponible()
, comprobará si el control asignado a la variable de tipoSupportMapFragment
se ha definido en el layout de la Activity:
1234567891011121314private void mapaDisponible(){if(mapFragment == null){mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);mapFragment.getMapAsync(Mapa.this);}if(mapFragment != null){Toast.makeText(this, "Mapa de Google disponible", Toast.LENGTH_SHORT).show();}} - Finalmente en este clase se implementa el método
onMapReady()
, implementado por la interfazOnMapReadyCallback
, que recibe como parámetro de entrada una variable de tipoGoogleMap
, y cuya tarea principal será la de convertir los valores de las coordenadas recibidas de la Activity anterior, en una dirección física dentro de Google Maps. Además de lo comentado anteriormente, desde este método se configurará tanto la posición de la cámara, como los datos que se mostrarán en el marcador que identifique la localización del dispositivo:
1234@Overridepublic void onMapReady(GoogleMap googleMap){- Se asignan y convierten los valores de latitud y longitud visualizados en la Activity principal a tipo double:
123456double dLongitud = Double.parseDouble(longitud);double dLatitud = Double.parseDouble(latitud);try{ - Se declara y crea un objeto de la clase Geocoder, encargada de transformar los datos de latitud y longitud obtenidos en una dirección física en el mapa (y viceversa):
1234Geocoder geoCoder = new Geocoder(this, Locale.getDefault());[...] - Se asigna el tipo de vista al mapa:
1234mapa.setMapType(GoogleMap.MAP_TYPE_NORMAL);[...] - Se habilita la localización del dispositivo en el mapa:
1234mapa.setMyLocationEnabled(true);[...] - Se comprueba que el número de direcciones almacenadas es mayor a 0:
1234if(addresses.size() > 0){address = addresses.get(0); - Se asignan los datos de dirección, código postal, ciudad y país:
123456direccionCoordenadas = address.getAddressLine(0)+ " " + address.getPostalCode()+ " " + address.getLocality()+ ", " + address.getCountryName();} - A continuación, se define el marcador que se mostrará en la posición dada, y que permitirá visualizar los datos en el mapa al pulsar sobre dicho marcador:
1234mapa.addMarker(new MarkerOptions().title(direccionCoordenadas).position(nuevaPosicion)); - En las siguientes líneas se indica la posición de la cámara para la visualización del mapa de Google, dónde además se establecen los ajustes de zoom, inclinación y dirección:
1234567CameraPosition cameraPosition = CameraPosition.builder().target(nuevaPosicion).zoom(16.0f).tilt(45.0f).bearing(45.0f).build(); - Por último se invoca al método encargado de actualizar el movimiento de la cámara y el tiempo de duración para actualizar dicho movimiento:
1234mapa.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), 2000, null);[...]
- Se asignan y convierten los valores de latitud y longitud visualizados en la Activity principal a tipo double:
Geolocalizacion\app\src\main\res\layout\activity_main.xml
A nivel de layout (activity_main.xml
), se implementan cinco controles de tipo TextView
(se mencionarán sólo aquellos controles destinados a ser asignados en el controlador), encargados de mostrar los datos de latitud, longitud, altitud y precisión de la posición del dispositivo, además de indicar si los datos recogidos son valores por defecto o proporcionados por el sistema de posicionamiento global (GPS), y mediante un control de tipo Button
, se enviarán dichos datos a la siguiente Activity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
[...] <TableLayout android:layout_width="fill_parent" android:layout_height="wrap_content"> <TableLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <TableRow android:layout_width="fill_parent" android:layout_height="fill_parent"> [...] <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" android:text="@string/sin_valores" android:id="@+id/tvPorDefecto" /> </TableRow> <TableRow android:layout_width="fill_parent" android:layout_height="fill_parent"> [...] <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:inputType="numberDecimal" android:id="@+id/tvLatitud" android:background="#ffc4ff97" android:hint="@string/latitud" /> </TableRow> <TableRow android:layout_width="fill_parent" android:layout_height="fill_parent"> [...] <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:inputType="numberDecimal" android:id="@+id/tvLongitud" android:background="#ff55d7ff" android:hint="@string/longitud" /> </TableRow> <TableRow android:layout_width="fill_parent" android:layout_height="fill_parent"> [...] <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:id="@+id/tvPrecision" android:hint="@string/precision" android:background="#96ffc72d" /> </TableRow> <TableRow android:layout_width="fill_parent" android:layout_height="fill_parent"> [...] <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:id="@+id/tvAltura" android:hint="@string/altura" android:background="#ffedbcff" /> </TableRow> </TableLayout> <TableLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <TableRow android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/localizar" android:id="@+id/btnLocalizar" /> </TableRow> [...] </TableLayout> </TableLayout> [...] |
Geolocalizacion\app\src\main\res\layout\activity_maps.xml
Al implementar una nueva Activity de tipo Google Maps, creará por defecto un layout formado por un fragment, que haciendo uso de las coordenadas obtenidas en la Activity principal, mostrará en un mapa de Google la localización del dispositivo GPS:
1 2 3 4 5 6 |
<fragment xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/map" tools:context="com.academiaandroid.geolocalizacion.Mapa" android:name="com.google.android.gms.maps.SupportMapFragment" /> |
Permisos declarados en AndroidManifest.xml
:
Por último recordar que será necesario declarar dentro del AndroidManifest.xml
los diferentes permisos necesarios tanto para la conexión de Internet, como el acceso al estado de red, la escritura en almacenamiento externo y la lectura de servicios de Google:
1 2 3 4 5 6 7 8 |
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/> <!-- Los permisos de ACCESS_COARSE_LOCATION/ACCESS_FINE_LOCATION no son requeridos para el uso de Google Maps Android API v2, aunque se recomiendan. --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> |
Implementación de key para servicio de mapas de Google:
AndroidManifest.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.academiaandroid.geolocalizacion"> [...] <application android:allowBackup="true" android:icon="@drawable/g4582" android:label="@string/app_name" android:theme="@style/AppTheme"> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="@string/google_maps_key"/> <activity... [...] </activity> </application> </manifest> |
Geolocalizacion\app\src\debug\res\values\google_maps_api.xml
1 2 3 4 5 6 7 |
<resources> [...] <string name="google_maps_key" templateMergeStrategy="preserve">AIzaSyAY_2t7h2cptQLKWCC8KoOcMyeIrEFHQkc</string> </resources> |
Nota: como ya se ha comentado en un tutorial anterior, será necesaria la creación de una key (clave) para el uso de los servicios de mapas de Google en una aplicación propia.
Descarga del proyecto
A continuación puedes descargar todo el código del proyecto:
DownloadEsta entrada tiene 4 comentarios
Los comentarios están cerrados.
Muy útil a la vez que bien explicado. Quizás comentar que a partir de la versión 6 de Android (API 23), las aplicaciones que utilizan la localización GPS requieren permisos en tiempo de ejecución y Android pide que lo contemples explícitamente en el código. Yo lo he solucionado de la siguiente forma.
/*Método encargado de actualizar la posición del dispositivo
GPS cuando este cambie de localización.*/
private void rastreoGPS() {
/*Se asigna a la clase LocationManager el servicio a nivel de sistema a partir del nombre.*/
locManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
/*Se declara y asigna a la clase Location la última posición conocida proporcionada por el proveedor.*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
else{
Location loc = locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
mostrarPosicion(loc);
}
}else{
Location loc = locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
mostrarPosicion(loc);
}
…
Un tutorial magnífico.
Gracias.
Muchas gracias Daniel por tu comentario.
Efectivamente a partir de Android 6.0 los permisos se gestionan en tiempo de ejecución. Hemos desarrollado un ejemplo sobre este tema (asignar permisos de lectura de contactos y estado del teléfono) en nuestros últimos tutoriales sobre esta versión Marshmallow: http://academiaandroid.com/android-6-0-desarrollo-app-con-gestion-de-permisos/
Daniel sabes donde puedo conseguir el proyecto ? estoy en uno donde quiero mostrar distintas ubicaciones y que se vayan actualizando
Gracias de antemano.
Eduardo, solo indicarte que publicamos recientemente un proyecto de Geolocalización realizado con Android Studio y para API 23, con gestión de permisos en tiempo de ejecución:
http://academiaandroid.com/geolocalizacion-obtencion-coordenadas-desde-app-android/
Saludos