Services

Un Service simplemente es un componente que normalmente se ejecuta en segundo plano y con el que podremos realizar tareas de larga ejecución sin necesidad de una interface de usuario. El servicio continuara su ejecución en segundo plano incluso si el usuario finaliza la aplicación.

Generalmente se dividen en dos grupos:
  • Started - una vez iniciado el servicio se podrá ejecutar de forma indefinida realizando una sola operación cada vez. Se inicia llamando a "startService()".
  • Bound - un servicio con el que podremos interactuar enviándole solicitudes, obteniendo resultados e incluso realizar comunicaciones IPC. Se inicia con "bindService()".
Toda la información a sido sacada de la pagina de desarrolladores: Services con algún retoque personal. Y como siempre al final del articulo tendremos una aplicación de ejemplo.


1. Ciclo de vida


Hay un método común para los dos tipos de servicios que es "onCreate()".  Normalmente configuraremos aquí nuestro servicio.

Los servicios "Started" se inician de forma indefinida en el método "onStartCommand()". Para detener el servicio deberemos llamar a "stopSelf()" desde el mismo servicio o a "stopService()" desde la activity o componente que lo puso en marcha.

En los servicios "Bound" se inicia su vida en el método "onBind()" que sera el encargado de enlazar el servicio con la activity o componente que lo llamo. Se ejecutara de forma indefinida hasta que llamemos a "unbindService()" desde la activity o componente que lo puso en marcha, de esta manera se cierra la conexión y se terminar el servicio, en este momento el servicio pasara al método "onUnbind()".

Otro método común para los dos servicios es "onDestroy()". Sera el lugar donde guardaremos los datos importantes y limpiaremos el servicio de los recursos que hayamos podido utilizar.



2. Fundamentos

Para crear un servicio lo primero que tenemos que hacer es declararlo en el "AndroidManifest.xml" utilizando la etiqueta "<service>" dentro de las etiquetas de nuestra aplicación:
<application .... >
    ....
    ....
        
    <service android:name="com.example.services.MiService1"/>

    <service android:name="com.example.services.MiService2">
        ....
        ....
    </service>
        
</application>
El único atributo obligado es "android:name" pero podríamos definir mas propiedades como por ejemplo "android:exported" que nos servirá para declarar si el servicio sera publico o privado. Indicando false, el servicio sera privado de uso exclusivo para la aplicación. Si por el contrario indicamos true, el servicio sera publico para el resto de aplicaciones, en este caso deberemos declarar el "<intent-filter>" correspondiente.

Una vez declarado el servicio en el manifiesto bastara crear una nueva clase y extender una de las clases "Service" o "IntentService" sobreescribiendo los métodos que necesitemos.



3. Servicios Started

Podríamos dividirlos en dos grupos, los que extienden la clase "Service" y los que extienden la clase "IntentService":
  • Service - es la clase base para crear cualquier servicio. Una vez iniciado el servicio se ejecutara de forma indefinida en segundo plano. Es importante crear un hilo para hacer todo el trabajo.
  • IntentService - es una subclase de service que maneja las peticiones una a una. Ideal si no necesitamos manejar multiples peticiones al mismo tiempo. Una vez termine de ejecutar la ultima petición automáticamente el servicio terminara.
Para iniciar este tipo de servicios lo haremos con el método "startService()" que automáticamente pondrá en marcha el servicio. Una vez iniciado su ciclo de vida es independiente del componente o activity que lo creo. Se ejecutara en segundo plano incluso si el componente o activity que lo inicio queda destruido. Por lo tanto deberá detenerse en algún momento llamando a "stopSelf()" en el mismo servicio o llamando a "stopService()" desde algún componente o activity.


3.1 IntentService

Lo primero que tenemos que hacer para crear este tipo de servicio es crear una nueva clase que extienda la subclase "IntentService" y crear un constructor:
public MiServiceIntent() {
    super("MiIntentService");
}
En el constructor simplemente devolvemos la superclase "super(String)" indicando como parámetro un identificador para dicho servicio.

Una vez creado el constructor simplemente sobreescribiremos el método "onHandleIntent()" que usaremos para realizar la tarea como por ejemplo descargar un archivo:
protected void onHandleIntent(Intent intent) {
    // Realizar la tarea aqui
}
De esta manera tendríamos creado un servicio que podríamos poner en marcha en cualquier momento. Pero sino vamos a sobreescribir mas métodos es recomendable como mínimo sobreescribir el método "onStartCommand()" y devolver la superclase por defecto:
public int onStartCommand(Intent intent, int flags, int startId) {
    return super.onStartCommand(intent,flags,startId);
}
Con esto conseguimos que el servicio gestione automáticamente el ciclo de vida de cada petición.

Una vez configurado el servicio podremos ponerlo en marcha desde una activity de la siguiente manera:
Intent intent = new Intent(MainActivity.this, MiService.class);
startService(intent);


3.2 Service

Prácticamente este tipo de servicio engloba al resto de servicios con la diferencia de que los servicios "Bound" simplemente son servicios de este tipo con lo que podemos enlazarnos y comunicarnos.

Para crear este tipo de servicio extenderemos la clase "Service" y sobreescribiremos los siguiente métodos:
public void onCreate(){
    // Configura aqui el servicio
}
Método en el que configuraremos nuestro servicio, por ejemplo podríamos crear un hilo que se encargue de descargar un archivo. Entonces cada petición que reciba la procesara al momento sin tener que esperar a que termine la anterior.

public int onStartCommand(Intent intent, int flags, int startId){
    return Service.START_STICKY;    
}
El método "onStartCommand()" debe devolver un entero. Dicho entero indicara la forma de actuar del servicio. En el ejemplo anterior hemos visto que devolviendo la superclase el servicio se autogestionaba procesando las peticiones una a una y matando al servicio al terminar la petición. En este caso podemos declarar una constante de las siguientes:
  • START_NOT_STICKY - el sistema mata al servicio al terminar la petición y no vuelve a crearlo a no ser que existan peticiones pendientes. Se suele utilizar para servicios que se activan periodicamente.
  • START_STICKY - el sistema mata al servicio al terminar la petición y lo vuelve a iniciar. Se utiliza para servicios que administran su propio estado y no dependen de los datos de petición.
  • START_REDELIVER_INTENT - el sistema mata al servicio al terminar la petición y lo vuelve a iniciar con los datos de la ultima petición.

Continuamos creando nuestro servicio sobreescribiendo el método "onBind()":
public IBinder onBind(Intent intent) {
    return null;
}
Este método normalmente devuelve un objeto "IBinder" con el que podremos comunicarnos con el servicio creando un servicio Bound. En este caso no lo utilizamos así que devolvemos nulo. En los siguientes puntos del articulo veremos su uso.

Para terminar el servicio sobreescribiremos el método "onDestroy()":
public void onDestroy(){
    // Limpiar servicio
}
Que utilizaremos para guardar datos si es necesario y limpiar los recursos generados en el servicio.

Este tipo de servicios se inician igual que el ejemplo anterior y podremos detenerlo en cualquier momento dentro del servicio llamando al método "stopSelf()" o incluso desde un componente o activity utilizando la siguiente declaración:
Intent intent = new Intent(MainActivity.this, MiService.class);
stopService(intent);

Una vez iniciado el servicio se estará ejecutando en segundo plano en nuestro terminal, podemos acceder a las opciones del terminal, aplicaciones en ejecución y veremos lo siguiente:



3.3 Service Foreground

Este tipo de servicios se ejecuta en primer plano por lo tanto el sistema podrá matarlo en caso de que la memoria sea baja. Normalmente se informara al usuario con una notificación permanente en la barra de estado. Y son realmente útiles por ejemplo para crear una aplicación de reproducción de medios como puede ser un reproductor de música. Si el usuario cierra el reproductor, el servicio seguirá ejecutandose y el usuario podrá volver a la aplicación pulsando la notificación.

Podríamos coger el servicio del ejemplo anterior y para ejecutarlo en primer plano utilizaremos la siguiente declaración:
startForeground(id, notification);
Como primer parámetro "id" nos pide un identificador para la notificación permanente. Y como segundo parámetro "notification"  nos pide la notificación a mostrar.

Para terminar este tipo de servicio lo podremos hacer con las siguiente declaración:
stopForeground(removeNotification);
Como parámetro nos pide un boolean que indicara si la notificación debe eliminarse. En caso de true la notificación desaparecerá y en caso de false la notificacion permanecerá en la barra de estado.

Una vez iniciado el servicio podríamos ver lo siguiente:




4. Servicios Bound

Este tipo de servicio permite la comunicación entre servicio y componente o actividad que lo inicio.  Nos permite crear varias conexiones a la vez. Para iniciar el servicio lo haremos a través del método "bindService()" y podremos comunicarnos con el a través de una interface "IBinder". Para terminar la conexión simplemente llamaremos al método "unbindService()".

Existen tres formas de comunicación con estos servicios:
  • IBinder - realmente útil cuando creamos un servicio privado para nuestra aplicación que se dedica a trabajar en segundo plano.
  • Messenger - es la forma mas sencilla de crear una comunicación entre procesos (IPC). El messenger o mensajero se encarga de poner todas las solicitudes en cola y en un único proceso.
  • AIDL - es el mas completo y complejo a la vez. Similar al anterior pero en este caso podemos manejar varias peticiones a la vez creando subprocesos.


4.1 Service IBinder

Se crea como cualquier servicio anterior, extendiendo la clase "Service" y deberemos seguir unos pasos para crear la conexión entre el componente o activity y el propio servicio.

En nuestro servicio lo primero que tenemos que hacer es crear una clase que extienda de "Binder" con un único método "getService()" que sera la encargada de proporcionar una instancia del servicio a la activity o componente:
public class MiBinder extends Binder {
    public MiService getService() {
        return MiService.this;
    }
}
Simplemente se crea la clase que extienda de Binder y se crea el método "getService()". Este método devuelve una instancia del servicio.

Lo siguiente es crear un objeto "IBinder" que sera el encargado de devolver los métodos públicos del servicio a los que podrá acceder a la activity o componente.
private final IBinder iBinder = new MiBinder();
Este objeto sera devuelto por el método "onBind()" cada vez que se cree una conexión con el servicio. El objeto simplemente almacena una instancia de la clase creada anteriormente.

Ya solo nos quedaría sobreescribir dos métodos mas:
public IBinder onBind(Intent arg0) {
    return iBinder;
}
El "onBind()" se utiliza cada vez que una activity o componente se conecte al servicio y se debe devolver el objeto iBinder creado antes.
public void onDestroy(){
    // Hacer algo aqui
}
Y para terminar el servicio sobreescribimos el método "onDestroy()".


Ahora vamos a configurar la actividad, primero tenemos que crear una instancia u objeto de nuestro servicio:
private MiService mService;
Para poder utilizar ese objeto y poder comunicarnos con el servicio primero debemos crear una interface "ServiceConection" que sera la encargada de enlazar el servicio con dicho objeto:
private ServiceConnection sConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        MiBinder binder = (MiBinder) service;
        mService = binder.getService();
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        mService = null;
    }
};
Esta interface nos obliga a sobreescribir dos métodos:
  • onServiceConnected() - cada vez que se cree una nueva conexión con el servicio actuara este método devolviendo la instancia del servicio en el objeto "mService".
  • onServiceDisconnected() - se utiliza cuando se a terminado la conexión con el servicio, por lo tanto tendremos que anular la instancia del servicio.

Una vez configurados todos los pasos podremos crear una conexión con nuestro servicio utilizando la siguiente declaración en cualquier lugar de nuestra activity:
Intent intent = new Intent(MainActivity.this, MiService.class);
bindService(intent, sConnection, Context.BIND_AUTO_CREATE);
Esta declaración automáticamente llamara al método "onBind()" de nuestro servicio y pondrá en marcha todos los pasos de este punto del articulo.

En cualquier momento podemos coger el objeto "mService" y realizar la comunicación.

Para terminar la conexión deberemos utilizar la siguiente declaración:
unbindService(sConnection);

Cosas a tener en cuenta:
  • Podemos realizar tantas conexiones como nos sean necesarias, pero solo la primera conexión llamara a "onBind()". En las conexiones posteriores el servicio simplemente devolverá el objeto IBinder.
  • Hasta que no se termine la ultima conexión con "unbindService()". El servicio seguirá funcionando en segundo plano de forma indefinida.
  • Si se inicio el servicio con "startService()", se ha creado la comunicación con "bindService()" y se a cerrado la comunicación con "unbindService()". El servicio seguirá en marcha hasta que  se detenga en el propio servicio con "stopSelf()" o directamente desde la activity con "stopService()". 


4.2 Service Messenger

Este tipo de servicios es similar al anterior pero en este caso la comunicación se realiza a través de mensajes. Por lo tanto el servicio deberá implementar un Handler que sera el encargado de recibir los mensajes y procesar las solicitudes. Prácticamente el handler sera el mensajero.

Vamos a empezar creando en el servicio una constante que sera el identificador de un único mensaje que vamos a crear para la comunicación:
public static final int MSG_SAY_HELLO = 1;
Lo siguiente es crear el Handler que sera el encargado de procesar los mensajes que reciba el servicio:
private static class MiHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_SAY_HELLO:
                Toast.makeText(context, "¡ Hola !", Toast.LENGTH_SHORT).show();
                break;
            default:
                super.handleMessage(msg);
        }
    }
}
En este caso el handler o manejador simplemente procesara un mensaje. Cuando el servicio reciba un mensaje con el identificador "MSG_SAY_HELLO", el handler automáticamente mostrara un toast.

Ahora deberemos crear un objeto "Messenger" en el que almacenaremos un nuevo Messenger:
final Messenger messenger = new Messenger(new MiHandler());
El nuevo objeto Messenger nos pide como parámetro un Handler, por lo tanto le aplicamos una instancia de nuestro Handler. Este objeto "messenger" sera devuelto por el método "onBind()".

Para terminar el servicio deberemos sobreescribir los métodos "onBind()" y "onDestroy()"
public IBinder onBind(Intent intent) {
    return messenger.getBinder();
}
Simplemente se devuelve el objeto "messenger" convertido en IBinder para poder realizar la comunicación.


La configuración de la activity es similar al ejemplo anterior, en este caso empezaremos creando una instancia de Messenger:
private Messenger messenger = null;
Sera la que utilicemos para enviar solicitudes al servicio.

En este caso también debemos implementar la interface "ServiceConnection"
private ServiceConnection sConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        messenger = new Messenger(service);
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        messenger = null;
    }
}; 
Y nos conectaremos con el servicio de la misma manera que el ejemplo anterior, a través del método "bindService()". De igual manera nos desconectaremos con "unbindService()".

Una vez conectado con el servicio podremos mandarle un mensaje de la siguiente manera:
Message msg = Message.obtain(null, MiServiceMessenger.MSG_SAY_HELLO, 0, 0);
try {
    messenger.send(msg);
} catch (RemoteException e) {}
Automáticamente lo recibirá el servicio y procesará la solicitud.


Este ejemplo a creado una comunicación de mensajería unidireccional en un único sentido. Para crear una comunicación de dos vías el cliente o activity deberá crear otro Handler para recibir las respuestas del servidor.



5. Mini Aplicación

DESCARGAR: APP SERVICES


No hay comentarios:

Publicar un comentario