Servicios >

Servicios Vinculados (Bound Services)

    Reseumen

    • Un servicio vinculado permite que otros componentes se vinculen a él, para interactuar y realizar una comunicación inter-proceso.
    • Un servicio vinculado es destruido una vez que todos los clientes se hayan desvinculados, a no ser que el servicio se haya iniciado.

    En este documento

    1. Básicos
    2. Crear un servicio vinculado (Bound Services)
      1. Extender la clase Binder
      2. Utilizar un mensajero
    3. Vinculación a un servicio
    4. Gestionar el ciclo de vida de un Servicio Vinculado (Bound Service)

    Clases clave

    1. Servicio
    2. ServiceConnection
    3. IBinder

    Ejemplos

    1. RemoteService
    2. LocalService

    Ver también

    1. Servicios

Un servicio vinculado es el servidor en una interfaz cliente-servidor. Permite que componentes (como las actividades) se vinculen a un servicio, manden peticiones, reciban respuestas, y realicen comunicaciones inter-procesos (IPC). Este tipo de servicio solamente existe mientras sirve a otro componente de una aplicación y no se ejecuta indefinidamente en un segundo plano.

Este documento muestra como crear un servicio vinculado, incluyendo como vincular componentes de la aplicación al servicio. Para ver información general sobre servicios, como entregar notificaciones desde un servicio, configurar el servicio para que se ejecute en primer plano, etc, ver el documento Servicios.

Básicos

Un servicio vinculado es una implementación de la clase Service que permite vincularse a él a otras aplicaciones así como interactuar con él. Para poder vincularse a un servicio, se debe implementar el método callback onBind(). Este método devuelve un objeto IBinder que define la intefaz que los clientes pueden usar para interactuar con el servicio.

Un cliente se puede vincular a un servicio llamando al método bindService(). Al hacerlo debe aportar una implementación del ServiceConnection, que monitoriza la conexión con el servicio. El método bindService() retorna inmediatamente sin un valor, pero cuando el sistema Android crea la conexión entre el cliente y el servicio, llama al método onServiceConnected() del ServiceConnection, para entregar el IBinder que el cliente va a poder utilizar para comunicarse con el servicio.

Varios clientes se pueden conectar al servicio a la vez. Sin embargo, el sistema llama al método onBind() del servicio para recuperar el IBinder solo cuando se vincula el primer cliente. El sistema entrega ese mismo IBinder a cualquier otro cliente que se vincula, sin volver a llamar al método onBind().

Cuando el último cliente se desvincula del servicio, el sistema destruye el servicio (a no ser que el servicio haya sido iniciado por el método startService()).

Cuando se implementa el servicio vinculado, la parte más importante es definir la interfaz que devuelve el método callback onBind(). Existen varias maneras de definir la interfaz del servicio IBinder que discutimos a continuación.

Crear un servicio vinculado.

Cuando se crea un servicio que puede ser vinculado, se debe suminitrar un IBinder que aporta la intefaz de programación que los clientes pueden utilizar para interactuar con el servicio. Existen tres maneras de definir la interfaz:

Extender la clase Binder
Si el servicio es privado de la aplicación y se ejecuta en el mismo proceso que el cliente (lo que es común), se debe crear una interfaz extendiendo la clase Binder y devolviendo una instancia suya desde el método onBind(). El cliente recibe el Binder y lo puede usar para acceder directamente a métodos públicos disponibles o bien en la implementación del Binder o incluso del Service.

Esta es la mejor técnica cuando el servicio es un trabajador en segundo plano de la aplicación. La única razón para no crear la interfaz de esta manera es si el servicio es utilizado por otras aplicaciones o en procesos separados.

Utilizar un mensajero (Messenger)
Si se necesita que la intefaz trabaje en varios procesos, se puede crear una interfaz para el servicio con un Messenger. De esta manera, el servicio define un Handler que responde a diferentes tipos de objetos Message objects. Este Handler es la base de un Messenger que puede compartir un IBinder con el cliente, permitiendo mandar al cliente comandos al servicio utilizando objectos Message. Adicionalmente, el cliente puede definir un Messenger propio para que el servicio pueda mandar mensajes de vuelta.

Esta es la manera más simple de realizar una comunicación inter-proceso (IPC), ya que el Messenger pone en cola todas las peticiones en un hilo único para que el servicio tenga seguridad en hilos (thread-safe).

Utilizar AIDL
El AIDL (Lenguaje de Definición de Intefaz Android) realiza todo el trabajo para descomponer objetos en primitivos que pueda entender el sistema operativo y reunirlos a través de los procesos para realizar IPC. La técnica anterior, que utiliza un Messenger, tiene como estructura de base un AIDL. Como se menciona anteriormente, el Messenger crea una cola con todas las peticiones del cliente en un solo hilo, para que el servicio reciba una sola petición a la vez. Si, por el contrario, se quiere que el servicio gestione varias peticiones a la vez, entonces se puede usar directamente AIDL. En este caso, el servicio debe ser capaz de hacer un trabajo multi-hilo y tiene que ser desarrollado de manera thread-safe.

Para usar directamente AIDL, se debe crear un archivo .aidl que define la interfaz de programación. Las herramientas de Android SDK utilizan este archivo para generar una clase abstracta que implementa la interfaz y gestiona la IPC, que se pueden extender dentro del servicio.

Nota: La mayoría de las aplicaciones no deberían utlizar AIDL para crear un servicio vinculado, ya que pueden necesitar capacidades multi-hilo y pueden crear una implementación más complicada. Como tal, AIDL no es adecuado para la mayoría de las aplicaciones y este documento no detalla como utilizarlo en los servicios. Si se necesita utilizar AIDL directamente, ver la documentación sobre AIDL.

Extender la clase Binder

Si el servicio es utilizado solamente por la aplicación local y no necesita trabajar a través de procesos, se puede implementar una clase Binder propia que le da al cliente acceso a métodos públicos en el servicio.

Nota: Esto funciona solo si el cliente y el servicio están en la misma aplicación y proceso, lo que es bastante común. Esto funcionaría, por ejemplo, para una aplicación de música que necesita vincular una actividad a su propio servicio que está ejecutando música en un segundo plano.

Así se configura:

  1. En el servicio, crear una instancia de Binder que bien:
    • contenga métodos públicos a los que el cliente pueda llamar
    • devuelva la instancia del Service en curso, la cual tiene métodos públicos a los que el cliente puede llamar
    • o bien, devuelve una instancia de otra clase hosteada por el servicio con métodos públicos a los que el cliente puede llamar
  2. Devolver esta instancia del Binder desde el método callback onBind().
  3. En el cliente, recibir el Binder desde el método callbackonServiceConnected() y hacer llamadas al servicio vinculado utilizando los métodos suministrados.

Nota: La razón por la que el servicio y el cliente debe estar en la misma aplicación es para que el cliente pueda castear el objeto devuelto y llamar su API correspondiente. El servicio y el cliente deben estar también en el mismo proceso, ya que esta técnica no consigue nada a través de diferentes procesos.

Como ejemplo, aquí tenemos un servicio que suministra acceso de clientes a métodos en servicios a través de la implementación del Binder:

public class LocalService extends Service {
    // Binder dado a los clientes
    private final IBinder mBinder = new LocalBinder();
    // Generador aleatorio de números
    private final Random mGenerator = new Random();

    /**
     * Clase utilizada por el cliente Binder.  Como se sabe que este servicio
     * se ejecuta siempre en el mismo proceso que sus clientes, no se necesita tratar con el IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // Devolver esta instancia del LocalService para que los clientes puedan llamar a métodos públicos
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /** método para clientes */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}

El LocalBinder suministra el método getService() para que los clientes recuperen la instancia del LocalServiceen curso. Esto permite a los clientes llamar a los métodos públicos del servicio. Los clientes, por ejemplo, pueden llamar al método getRandomNumber() desde el servicio.

Aquí se muestra una actividad que se vincula al LocalService y llama al método getRandomNumber() cuando se hace click en un botón:

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

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

    @Override
    protected void onStart() {
        super.onStart();
        // Se vincula al LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // se desvincula del servicio
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /** Se llama cuando se hace click en un botón (el botón en el archivo layout se une a
      * este método con el atributo android:onClick) */
    public void onButtonClick(View v) {
        if (mBound) {
            // Llama a un método desde el LocalService.
            // Sin embargo, si en esta llamada hubiera algo suspendido, entonces la petición debería
            // realizarse en un hilo por separado para evitar la ralentización de la actividad.
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** Define los callback pasados al método bindService() para la vinculación de los servicios*/
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // Se ha hecho la vinculación al LocalService, el cast del IBinder y un get de la instancia del LocalService
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}

El ejemplo anterior muestra como el cliente se une al servicio utilizando la implementación del ServiceConnection y del método callback onServiceConnected(). La siguiente sección nos aporta más información sobre este proceso de vinculación al servicio.

Nota: En el ejemplo anterior no se desvincula explicitamente del servicio, pero todos los clientes deberían desvincularse en el momento adecuado (cuando la actividad se pone en pausa).

Para más ejemplos de código, ver la clase LocalService.java y la clase LocalServiceActivities.java en ApiDemos.

Utilizar un Messenger

Si se necesita que el servicio se comunique con procesos remotos, entonces se puede usar un Messengerpara suministrar la interfaz para el servicio. Esta técnica permite que se realice una comunicación interproceso (IPC) sin la necesidad de usar AIDL.

A continuación hay un resumen sobre como utilizar un Messenger:

De esta manera, no hay "métodos" del servicio para que el cliente los llame. En vez de esto, el cliente entrega "mensajes" (objetos Message) que el servicio recibe en su Handler.

Aquí hay un ejemplo simple de un servicio que utiliza la interfaz Messenger:

public class MessengerService extends Service {
    /** Comando para que el servicio muestre un mensaje */
    static final int MSG_SAY_HELLO = 1;

    /**
     * Handler de mensajes que vienen de los clientes.
     */
    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    /**
     * Diana que publicamos para que los clientes manden mensajes al IncomingHandler.
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /**
     * Cuando se vincula al servicio, se devuelve una interfaz a nuestro mensajero
     * para mandar mensajes al servicio.
     */
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        return mMessenger.getBinder();
    }
}

Nótese que el método handleMessage() en el Handler es donde el servicio recibe el Message entrante y decide que hacer, basándose en el miembro what.

Todo lo que un cliente necesita hacer es crear un Messenger basándose en el IBinder devuelto por el servicio y mandar un mensaje utilizando send(). Aquí tenemos, como ejemplo, una simple actividad que se vincula al servicio y entrega el mensaje MSG_SAY_HELLO al servicio:

public class ActivityMessenger extends Activity {
    /** Mensajero para comunicarse con el servicio. */
    Messenger mService = null;

    /** Flag indicando si se ha llamado a bind en el servicio. */
    boolean mBound;

    /**
     * Clase para interaccionar con la interfaz principal del servicio.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            // Se llama a este método cuando la conexión con el servicio ha sido
            // establecida, dándonos el objeto que podemos utilizar
            // para interactuar con el servicio. Nos estamos comunicando con el
            // servicio utilizando un Messenger, por lo que aquí obtenemos una representación
            // del lado del cliente del objeto IBinder.
            mService = new Messenger(service);
            mBound = true;
        }

        public void onServiceDisconnected(ComponentName className) {
            // Se llama a este método cuando la conexión con el servicio ha sido
            // desconectada inesperadamente-- su proceso ha sido termindado.
            mService = null;
            mBound = false;
        }
    };

    public void sayHello(View v) {
        if (!mBound) return;
        // Crear y mandar un mensaje al servicio, utilizando un valor 'what'.
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        try {
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

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

    @Override
    protected void onStart() {
        super.onStart();
        // Vincular al servicio
        bindService(new Intent(this, MessengerService.class), mConnection,
            Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Desvincular del servicio
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
}

Nótese que este ejemplo no muestra como el servicio puede responder al cliente. Si se quiere que el servicio responda, entonces se necesita crear también un Messenger en el cliente. Cuando el cliente recibe el callback onServiceConnected(), manda un Message al servicio que incluye el Messenger del cliente en el parámetro replyTo del método send().

Se puede ver un ejemplo de como suministrar mensajes en los dos sentidos en los ejemplos del MessengerService.java (servicio) y MessengerServiceActivities.java (cliente).

Vincular a un servicio

Los componentes de una aplicación (clientes) pueden vincularse a un servicio llamando al método bindService(). El sistema Android llama en ese momento al método onBind() del servicio, que devuelve un IBinder para interactuar con el servicio.

La vinculación es asíncrona. bindService() devuelve inmediatamente y no devuelve al cliente el IBinder. Para recibir el IBinder, el cliente debe crear una instancia de ServiceConnection y pasárselo a bindService(). El ServiceConnection incluye un método callback al que llama el sistema para entregar el IBinder.

Nota: Solamente las actividades, servicios y los content providers pueden vincularse a un servicio— no se puede vincular desde un broadcast receiver a un servicio.

Para vincularse a un servicio desde un cliente, se debe:

  1. Implementar el ServiceConnection.

    La implementación debe sobreescribir dos métodos callback:

    onServiceConnected()
    El sistema lo llama para entregar el IBinder devuelto por el método onBind() del servicio.
    onServiceDisconnected()
    El sistema Android lo llama cuando la conexión al servicio se pierde inesperadamente, como por ejemplo cuando el servicio se bloquea o se elimina. No se llama cuando el cliente se desvincula.
  2. Llamar al método bindService(), pasando la implementación del ServiceConnection.
  3. Cuando el sistema llama al método callback onServiceConnected() se puede iniciar la llamada al servicio, utilizando métodos definidos por la interfaz.
  4. Para desconectarse del servicio, hay que llamar al unbindService().

    Cuando el cliente es destruido, se desvinculará del servicio, pero se debería desvincular siempre que se termine la interacción con el servicio o cuando la actividad se pone en pausa para que el servicio se puede apagar mientras no se esté usando.(Más adelante se hablará de cuando se debe desvincular y vincular.)

El siguiente snippet conecta el cliente con el servicio creado anteriormente a través de la extensión de la clase Binder, por lo que tiene que hacer simplemente es castear el IBinderdevuelto a la clase LocalService y pedir una instancia de LocalService:

LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Se llama cuando se establece la conexión con el servicio 
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Dado que se ha producido la vinculación a un servicio
        // que se está ejecutando en nuestro proceso, se puede
        // castear el IBinder a una clase concreta y acceder directamente a ella. 
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
    }

    // Se llama cuando la conexión con el servicio se desconecta inesperadamente
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false;
    }
};

Con este ServiceConnection, el cliente se puede vincular a un servicio pasándoselo al bindService(). Por ejemplo:

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  • El primer parámetro del bindService() es un Intent que explícitamente nombra al servicio al que se vincula (a pesar de que el intent puede ser implícito).
  • El segundo parámetro es el objeto ServiceConnection.
  • El tercer parámetro es un flag indicando las opciones para la vinculación. Para crear el servicio si no está todavía vivo, debería ser BIND_AUTO_CREATE. Otros posibles valores son BIND_DEBUG_UNBIND y BIND_NOT_FOREGROUND..

Notas adicionales

A continuación se detallan unas notas sobre como vincularse a un servicio:

  • Se deberían manejar siempre las excepciones DeadObjectException, que son lanzadas cuando la conexión se rompe. Esta es la única excepción lanzada por métodos remotos.
  • Los objetos son referencias enumeradas a través de los procesos.
  • Se debería emparejar los vinculados y desvinculados durante los momentos correspondientes de ejecución y parada del ciclo de vida de los clientes. Por ejemplo:
    • Si solamente se necesita interactuar con el servicio mientras la actividad es visible, se debería hacer la vinculación durante onStart() y la desvinculación durante onStop().
    • Si se quiere que la actividad reciba respuestas mientras está parado en un segundo plano, se puede vincular durante onCreate() y desvincular durante onDestroy(). Hay que tener en cuenta que esto implica que la actividad necesita usar al servicio durante todo el tiempo de ejecución (incluso en un segundo plano), por lo que si el servicio está en otro proecos, esto hace que se aumente el peso del proceso y se aumente la probabilidad de que el sistema lo mate.

    Note: No se debería vincular y desvincular durante los métodos onResume() y onPause() de la actividad, ya que estos callbacks son llamados en cada transición del ciclo de vida y se deberían mantener al mínimo estas transiciones. Si diversas actividades de la aplicación se vinculan al mismo servicio y no existe transición entre dos de esas actividades, el servicio puede ser destruido y recreado cuando la actividad en curso se desvincule (durante una pausa) antes de que la siguiente se vincule (durante la reanudación). (Como funciona la transición en la actividad para que las actividades coordine su ciclo de vida está descrito en: Activities)

Para más ejemplo de código, de cómo vincularse a un servicio, ver la clase RemoteService.java en ApiDemos.

Gestionar el ciclo de vida de un Servicio Vinculado

Figura 1. Ciclo de vida de un servicio que está iniciado y que permite vinculación.

Cuando un servicio está desvinculado de todos los clientes, el sistema Android lo destruye (a no ser que haya sido iniciado con onStartCommand()). De esta manera, no se tiene que gestionar el ciclo de vida del servicio si es puramente un servicio vinculado—el sistema Android lo gestiona por ti basándose en si está vinculado a alguno de los clientes.

Sin embargo, si se escoge implementar el método callback onStartCommand(), se debe para el servicio explícitamente, ya que el servicio está considerado como iniciado. En este caso, el servicio se ejecuta hasta que el servicio se para a si mismo con el método stopSelf() o si otro componente llama al método code>stopService(), sin importar si está vinculado a algún cliente.

Además, si el servicio está iniciado y acepta ser vinculado, cuando el sistema llama al método onUnbind(), se puede devolver opcionalmente true si la próxima vez que un cliente se vincule a un servicio, se quiere recibir una llamada al método onRebind() (en vez de recibir una llamada al onBind()). onRebind() devuelve void, pero el cliente sigue recibiendo el IBinder en su callback onServiceConnected(). La figura 1, ilustra la lógica de este tipo de ciclo de vida.

Para más información sobre el ciclo de vida de un servicio iniciado, ver el documento Services.

↑ Ir arriba

← Back to Services