martes, 16 de abril de 2013

Android: Posicionamiento GPS y por triangulación de antenas


En el día de hoy aprenderemos como averiguar cuál es la posición de nuestro dispositivo de dos formas distintas: por la posición que nos indique el GPS (GPS_PROVIDER) y por el cálculo de la triangulación de antenas a las que conectamos (NETWORK_PROVIDER) que, aunque es un valor estimado, es más rápido y preciso en zonas con muchas antenas (ciudad).

Creación del proyecto y preparación de permisos

Para nuestro desarrollo crearemos un nuevo proyecto Android demoGPS, con su pantalla por defecto.



Una vez creado, abriremos su fichero AndroidManifest.xml para añadir los permisos de acceso a internet. Pulsamos sobre Add y  elegimos Uses Permission, y tras esto, en el desplegable lateral escogeremos android.permission.ACCESS_FINE_LOCATION.



Dibujando la pantalla

Nuestro ejemplo va a ser muy simple: añadiremos un botón para que nos muestre las coordenadas según el posicionamiento GPS y por triangulación, y 4 textos para mostrar la latitud y longitud de cada una (sus coordenadas).



Resumiendo:
  • Button con id btnGetPos y texto "Obtener Posiciones"
  • TextView con texto "Posición por GPS" y textAppearance "textAppearanceLarge"
  • TextView con id txtLatitudGPS
  • TextView con id txtLongitudGPS
  • TextView con texto "Triangulación de Antenas" y textAppearance="textAppearanceLarge"
  • TextView con id txtLatitudNet
  • TextView con id txtLongitudNet


Y en código, el layout nos queda:

<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/btnGetPos"
        android:layout_centerHorizontal="true"
        android:text="Posición por GPS"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/txtLongitudGPS"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/txtLatitudGPS"
        android:layout_below="@+id/txtLatitudGPS"
        android:layout_marginTop="18dp"
        android:text="Longitud:" />

    <TextView
        android:id="@+id/txtLongitudNet"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/txtLatitudNet"
        android:layout_below="@+id/txtLatitudNet"
        android:layout_marginTop="17dp"
        android:text="Longitud:" />

    <TextView
        android:id="@+id/txtLatitudNet"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/btnGetPos"
        android:layout_below="@+id/TextView02"
        android:layout_marginTop="15dp"
        android:text="Latitud:" />

    <TextView
        android:id="@+id/txtLatitudGPS"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView1"
        android:layout_below="@+id/textView1"
        android:layout_marginTop="15dp"
        android:text="Latitud:" />

    <TextView
        android:id="@+id/TextView02"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/txtLongitudGPS"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="28dp"
        android:text="Triangulación de Antenas"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <Button
        android:id="@+id/btnGetPos"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignRight="@+id/textView1"
        android:layout_marginTop="40dp"
        android:text="Obtener Posiciones" />

</RelativeLayout>

Listener de posicionamiento

Tal y como está definido en Android el gestor de posicionamiento, éste nos lanzará un evento de actualización cada vez que se produzca una actualización de posicionamiento o relacionada con el dispositivo que elijamos (de posicionamiento). Por ello, crearemos una clase listener que escuchará esas actualizaciones y que implementará la interfaz LocationListener que Android tiene definida para este tipo de tareas.

Crearemos entonces una clase que nombraremos DemoLocationListener y que implementará la clase android.location.LocationListener.



Al crear la clase nos aparecerán los siguientes métodos que tendremos que definir:
  • onLocationChanged: nos informa de cambios de localización.
  • onProviderDisabled: nos informa cuando el proveedor (GPS_PROVIDER / NETWORK_PROVIDER) pase a estar deshabilitado.
  • onProviderEnabled: Cuando el proveedor se haya habilitado.
  • onStatusChanged: Cambios de estado. No lo usaremos.


Así que vamos al lío: primero crearemos un constructor al que le pasaremos los TextView que queremos actualizar. Esto conlleva que debemos definir dos parámetros de tipo TextView en la clase.

private TextView latitud;
private TextView longitud;

public DemoLocationListener(TextView latitud, TextView longitud)
{
       super();
       this.latitud = latitud;
       this.longitud = longitud;
      
}

Tras esto, definiremos el método onLocationChanged, que lo único que hará será obtener la posición y actualizar los TextView’s.

@Override
public void onLocationChanged(Location arg0) {
       arg0.getLatitude();
       arg0.getLongitude();
       this.setLatitud(Double.toString(arg0.getLatitude()));
       this.setLongitud(Double.toString(arg0.getLongitude()));
}


private void setLatitud(String str){
       this.latitud.setText("Latitud: " + str);
}

private void setLongitud(String str){
       this.longitud.setText("Longitud: " + str);
}

En los métodos onProviderEnabled y onProviderDisabled simplemente modificaremos el texto de los TextViews para que nos muestre un mensaje.

@Override
public void onProviderDisabled(String provider) {
       this.setLatitud("GPS Deshabilitado");
       this.setLongitud("GPS Deshabilitado");
}

@Override
public void onProviderEnabled(String provider) {
       this.setLatitud("GPS Habilitado");
       this.setLongitud("GPS Habilitado");
}


Y con esto ya lo tendríamos todo listo. Ahora queda definir la clase que maneja a nuestro layout.

Codificando nuestra pantalla

En el layout hemos dibujado varios elementos, de ellos, solo 5 son importantes para manejar por código: los 4 TextViews de latitud/longitud y el botón, así que definiremos esas 4 propiedades en nuestra clase MainActivity:

public class MainActivity extends Activity {

       private Button btnGetPos;
       private TextView txtLatitudGPS;
       private TextView txtLongitudGPS;
       private DemoLocationListener gpsListener;
       private TextView txtLatitudNet;
       private TextView txtLongitudNet;
(...)
}

Como hemos hecho hasta ahora, en el método onCreate haremos una llamada a la función cargaObjetosPantalla.

@Override
protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
      
       cargaObjetosPantalla();
}

Esta función obtendrá los objetos de nuestro layout y después creará un nuevo evento para el botón.

private void cargaObjetosPantalla(){
       //obtenemos los objetos de la pantalla
       btnGetPos = (Button)findViewById(R.id.btnGetPos);
       txtLatitudGPS = (TextView)findViewById(R.id.txtLatitudGPS);
       txtLongitudGPS = (TextView)findViewById(R.id.txtLongitudGPS);
       txtLatitudNet = (TextView)findViewById(R.id.txtLatitudNet);
       txtLongitudNet = (TextView)findViewById(R.id.txtLongitudNet);
      
      
       btnGetPos.setOnClickListener(new OnClickListener() {
            
             public void onClick(View v) {
                    (...)
             }
       });
}

En el evento onClick del botón es donde tendremos toda la “chicha” de nuestro código:
1.       Obtenemos el LocationManager de la aplicación

//obtenemos nuestro LocationManager
LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);


2.       Creamos un listener en base a nuestra clase DemoLocationListener para el dispositivo GPS que llamaremos gpsListener.

//creamos nuestro listener de GPS
DemoLocationListener gpsListener = new DemoLocationListener(txtLatitudGPS, txtLongitudGPS);


3.       A nuestro LocationManager le solicitamos que cuando se produzcan actualizaciones sobre el dispositivo GPS (GPS_PROVIDER) nos informe por el listener gpsListener.

//asignamos el listener de GPS
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, gpsListener);

4.       Repetimos el paso 2 para crear nuesto netListener.

//creamos nuestro listener de GPS
DemoLocationListener netListener = new DemoLocationListener(txtLatitudNet, txtLongitudNet);


5.       Repetimos el paso 3 para que nos informe de cambios por triangulación (NETWORK_PROVIDER) con el listener netListener.

//asignamos el listener de GPS
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, netListener);
  
6.       Finalmente, y solo por estética, borramos el botón.

//deshabilitamos el botón
btnGetPos.setVisibility(4);


Y con esto, tendremos nuestra aplicación lista para probar. Y aquí es cuando veréis que las coordenadas por triangulación son bastante más rápidas que por GPS (al menos en mi teléfono chino).




Podéis descargaros el código aquí.