Las aplicaciones Android se programan en Java, y el entorno de desarrollo está enfocado al uso de Eclipse, por lo que es una plataforma bastante accesible para aquellos que estamos acostumbrados a trabajar con Java, ya sea web o en aplicaciones stand-alone.
En la documentación de desarrollo de Android se explica cómo instalar el plugin de Eclipse, además de los pasos previos, como la instalación de una versión soportada de Eclipse y el SDK de Android. Existen muchos recursos en Internet para empezar a programar aplicaciones, y dicha documentación es uno de los más importantes, porque además contiene la referencia de la API para todas las versiones de la plataforma.
Pero ciertas funcionalidades no están tan bien documentadas, o puede ser necesario recopilar información de múltiples sitios y realizar muchas pruebas para conseguir implementarla. Un claro ejemplo de ello es la programación de un servicio, no visual, que se inicie con el arranque del terminal.
La arquitectura de Android tiene un buen número de entidades. Las Activity vienen a representar ventanas o pantallas con las que el usuario interacciona, y suelen contener gran parte del código de las aplicaciones. Los Service están enfocados a la realización de tareas a más largo plazo, no necesariamente con la participación directa del usuario. Aquí vamos a programar un servicio que localiza geográficamente el terminal y hace algo con esa información. Otra pieza interesante del ciclo de vida de las aplicaciones Android son los BroadcastReceiver, clases que reciben todo tipo de eventos del sistema (la llegada de un SMS o de una llamada, la conexión o desconexión a una fuente de alimentación, el enchufado de los auriculares,…). En nuestro caso particular, usaremos el evento de arranque completo del terminal para iniciar nuestro servicio de localización.
Gran parte de la configuración de las aplicaciones Android se realiza de forma declarativa en el fichero XML de manifiesto, AndroidManifest.xml. Éste sería el manifiesto de nuestra aplicación.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.javisjava.android.locator"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<receiver android:name="com.javisjava.android.locator.StartupIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
<category android:name="android.intent.category.HOME"></category>
</intent-filter>
</receiver>
<service android:name="com.javisjava.android.locator.LocatorService">
<intent-filter>
<action android:name="com.javisjava.android.locator.LocatorService"></action>
</intent-filter>
</service>
</application>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
Podemos apreciar en la sección en que se define el Receiver, que el evento que está preparado para gestionar es la finalización del arranque del sistema operativo del terminal (BOOT_COMPLETED), y que el tipo de actividad que lo gestionará puede ejecutarse en la home del sistema o …
Después se define el servicio que queremos que se ejecute, y se le otorga un nombre, en este caso la cadena "com.javisjava.android.locator.LocatorService".
Todavía no hemos visto cómo el Receiver inicia el Service, ni qué hace éste. Vamos a ver el código del primero.
package com.javisjava.android.locator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class StartupIntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent serviceIntent = new Intent();
serviceIntent.setAction("com.javisjava.android.locator.LocatorService");
context.startService(serviceIntent);
}
}
Los BroadcastReceiver reciben el evento para el que están configurados en el manifiesto en su método onReceive. En eventos más complejos, como la llegada de un SMS, hay un extra de información (como el remitente y el contenido del mensaje) que va contenida en el objeto de tipo Intent. En este caso no es necesaria más información, sabemos que el sistema se ha arrancado porque ha llegado el evento. El servicio se inicia por su nombre, el mismo que se le asignó en el manifiesto. El código del servicio es el siguiente.
package com.javisjava.android.locator;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
public class LocatorService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, "Service Created", Toast.LENGTH_LONG).show();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Avoids service termination
startForeground(0, null);
Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
LocationManager locationManager =
(LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 5 * 60 * 1000, 50, locationListener);
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 5 * 60 * 1000, 50, locationListener);
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
LocationManager locationManager =
(LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
locationManager.removeUpdates(locationListener);
Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();
}
LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
// Called when a new location is found by the location providers
Log.i("Locator", location.toString());
}
public void onProviderEnabled(String provider) {
}
public void onProviderDisabled(String provider) {
}
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
}
En el método onCreate se puede incluir código que se ejecuta cuando el servicio se crea (se instancia). Hemos utilizado la clase Toast en él y en otros para presentar mensajes rápidos en la pantalla del terminal.
En el método onStartCommand es donde se indica qué queremos que haga nuestro servicio. Son especialmente importantes la ejecución de startForeground y la devolución de START_STICKY para que el sistema no pare y deseche el servicio cuando relice labores de recolección de memoria, y, en caso de que lo haga, lo vuelva a iniciar en el estado en el que se encontraba, respectivamente.
Como se puede observar, el servicio registra un objeto de una clase interna anónima que implementa el interfaz LocationListener para que reciba las actualizaciones de la posición de los dos proveedores que soporta Android: el GPS y el cálculo basado en las redes wifi que inventó Google. En el ejemplo, se ha configurado para indicar (no es totalmente preciso) que reciba actualizaciones cada cinco minutos, y sólo si la nueva posición dista más de 50 metros de la anterior que se envió al Listener. Si bajamos al éste último, vemos que lo único que hace es enviar los datos de la nueva posición al log, que puede verse en la vista LogCat de Eclipse con el móvil conectado por USB (si previamente se ha activado el debugging USB en los ajustes de aplicaciones en el propio terminal). En un ejemplo más complejo, estos datos podrían enviarse por HTTP a un servidor, junto con la identificación única de terminal o el número de SIM. Así funcionan algunas aplicaciones que permiten localizar terminales perdidos o robados, e incluso resetearlos a valores de fábrica borrando todos los datos de manera remota.
Finalmente, en el método onDestroy, liberamos al sistema de enviar más actualizaciones al Listener. En nuestro caso, el servicio está pensado para apagarse sólo cuando lo hace el terminal, pero también podría pararse desde el control de servicios activos en los ajustes del sistema, y además es bueno acostumbrarse a liberar los recursos y a cerrar las conexiones que ya no se utilizan, en cualquier entorno, pero especialmente en dispositivos con memoria y procesador más modestos que los ordenadores de escritorio o los servidores.
Si volvemos una última vez al manifiesto, observamos que se definen dos permisos necesarios para que la aplicación funcione, el de recepción del evento de arranque del sistema, y el acceso a la geolocalización de forma precisa. Cuando una aplicación se sube al Android Market, el usuario que la descarga es avisado y debe aceptar otorgar los permisos que ésta necesita.
Adjunto el proyecto Eclipse con el código de la aplicación Android.
Hola Javi, el enlace al código está roto (404).
Me gustó el artículo.
Un saludo
Gracias, Antonio.
Ya está corregido.