Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Tema 10. Proveedores de Contenido y Servicios
Tema 10. Proveedores de Contenido y Servicios
Tema 10
Proveedores de Contenido
Algunos de los proveedores de contenido que ya implementa Android 1 son los siguientes:
1
Los proveedores de contenido que proporciona Android pueden consultarse en:
http://developer.android.com/reference/android/provider/package-summary.html
En general, los proveedores de contenido presentarán los datos a otras aplicaciones en forma
de una o varias tablas, de forma similar a las tablas de una base de datos relacional. Cada fila
de la tabla representará los datos de una instancia del tipo de objeto que el proveedor de
contenido recopila, y cada columna representará cada uno de los tipos de datos que el tipo de
objeto contiene.
En la tabla anterior, cada fila representa una instancia de un contacto, mientras que cada
columna representa cada uno de los datos almacenados en el contacto. Los nombres de la
cabecera de la tabla son los nombres que almacena el proveedor de contenido y que servirán
para localizar y acceder a cada uno de los datos.
La columna _ID representa la clave primaria para acceder a cada una de las instancias del tipo
de objeto que recopila el proveedor y será utilizada automáticamente por ListView en caso
de enlazar los resultados con este tipo de vista 2.
2
La clave primaria no suele faltar en las tablas de una base de datos relacional. No obstante, un proveedor de
contenido podría no tener clave primaria (es decir, no tendría la columna _ID).
3
Todos los proveedores de contenido extienden de ContentProvider.
El objeto ContentResolver actúa, por lo tanto, como una capa de abstracción entre los datos
de un repositorio y la representación externa de los mismos.
Para acceder a los datos de un proveedor de contenido, se invocará al método query() del
ContentProvider, que recibirá los siguientes métodos:
Por ejemplo, en el caso de querer acceder desde una actividad a la agenda para obtener una
lista de contactos, se implementaría una consulta similar a la siguiente:
URIs de contenido
Una URI de contenido es una URI 4 que identifica datos de un proveedor de contenido. Incluye
el nombre del proveedor de contenido (la autoridad) así como el nombre de la tabla (el path).
La constante CONTENT_URI del ejemplo anterior contiene la URI de la tabla de Contactos del
dispositivo, la cual es pasada como parámetro al ContentResolver, quien analizará la
autoridad contenida en la URI contrastándola con las tablas del sistema, para averiguar a qué
proveedor de contenido pasarle el resto de parámetros que recibe. El ContentProvider
analizará el path contenido en la URI para determinar las tablas que consultará, y utilizará en la
consulta el resto de parámetros que le ha pasado el ContentResolver. En general, cada
proveedor de contenido tiene un path por cada tabla que expone.
4
Uniform Resource Identifier, cadena corta de caracteres que identifica inequívocamente un recurso
En la mayoría de los casos, los proveedores de contenido permiten acceder a una fila concreta
de sus tablas añadiendo, al final de la URI, el valor del identificador de la columna _ID. Esta
característica permitirá actualizar o borrar filas de una tabla de un proveedor de contenido
desde las aplicaciones. Para ayudar a construir URIs bien formadas desde cadenas de texto,
Android provee las clases Uri, Uri.Builder y ContentUris.
Por ejemplo, para concatenar un _ID y extraer la URI de un contacto concreto, se utilizaría un
código similar al siguiente 5:
Como todas las operaciones que pueden llegar a ser largas, las consultas a proveedores de
contenidos deben realizarse siempre en un segundo hilo que no bloquee el hilo principal de la
aplicación, para que la interfaz de usuario no se quede bloqueada. Por lo tanto, en general, las
consultas a proveedores de contenido se asociarán a CursorLoader 6.
Para poder acceder a los proveedores de contenido, primero es necesario solicitar permiso a
través del manifiesto de la aplicación 7, ya que no se puede solicitar el mismo en tiempo de
ejecución. Para cada proveedor se deberá solicitar el permiso adecuado, en función de las
operaciones que vaya a realizar la aplicación sobre los datos que obtenga del mismo. Por
ejemplo, en el caso de querer visualizar los contactos del teléfono, una aplicación deberá
declarar el siguiente permiso en el manifiesto:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
5
Lo cual dará como resultado una Uri con el formato content://authority/path/id
6
Es necesario recordar que un CursorLoader es un Loader que pasa la consulta al ContentResolver y que
devuelve un Cursor.
7
La confirmación de estos permisos será responsabilidad del usuario propietario del dispositivo que deberá
aceptarlos al instalar la aplicación. Si no acepta las solicitudes de permisos que se mostrarán al instalar la aplicación,
la instalación no se realizará.
Filtros
Como en toda consulta a una tabla, se pueden establecer filtros que se pasarán al método
query(), a través de sus parámetros Selección (String) y Argumentos de la selección
(String[]), previamente explicados.
Por ejemplo, se podría implementar una vista de tipo EditText en la cual poder introducir un
texto para filtrar la lista de contactos cargada. Teniendo en cuenta que el parámetro Selección
corresponde a la cláusula WHERE, para la tabla de la consulta, en este caso sería:
Es necesario comentar que el lenguaje SQL permite también crear cláusulas WHERE
directamente a través del parámetro Selección como, por ejemplo:
Sin embargo, este tipo de consultas son peligrosas debido a que permiten la inyección de
código malicioso 8 en la propia consulta SQL, por lo que nunca deberán ser utilizadas.
La forma más óptima de mostrar los resultados de una consulta será a través de un
SimpleCursorAdapter que mostrará los resultados en una ListView. Por ejemplo, en el caso
de la consulta realizada a la agenda de contactos, se implementaría un código similar al
siguiente:
8
Por ejemplo, si en el filtro se escribe “nothing; drop table PHONE”, la query que se obtendría sería “SELECT *
FROM TABLE PHONE WHERE DISPLAY_NAME = nothing; drop table PHONE”, inyectándose la sentencia “drop
table PHONE” que borraría todos los datos de la tabla PHONE.
9
Que implementará el patrón Observer el cual define una dependencia del tipo 1-N entre objetos, de manera que
cuando uno de los objetos cambia su estado, el observador notifica este cambio a todos los objetos dependientes.
// Lista de IDs que contendrán las columnas del Cursor para cada fila
int[] itemsListaContactos = {
R.id.nombreContacto,
R.id.tipoTelefono,
R.id.numeroTelefono,
R.id.idContacto};
// Creación de un SimpleCursorAdapter
mAdaptadorContactos = new SimpleCursorAdapter(
getApplicationContext(), // Contexto de la aplicación
R.layout.lista_contactos, // Layout en XML para una
// fila en el ListView
mCursorContactos, // Resultado de la consulta al
//proveedor de contenidos
columnasListaContactos, // Array de strings de los
// nombres de las columnas en el
// cursor
itemsListaContactos);
Inserción
10
Es decir, una URI con el formato content://authority/path/id. Se podrá obtener el _ID con
ContenUris.parseID()
valoresNuevoContacto.put(
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, "Name Primary");
valoresNuevoContacto.put(
ContactsContract.Contacts.DISPLAY_NAME, "Name ");
valoresNuevoContacto.put(
ContactsContract.Contacts.HAS_PHONE_NUMBER, "No");
Actualización
valoresUpdateContactos.putNull(ContactsContract.Contacts.DISPLAY_NAME);
Borrado
Existen diversas formas de obtener los datos de un proveedor de contenido, incluso sin la
necesidad de solicitar los permisos necesarios a través de la publicación de los mismos en el
manifiesto de la aplicación. En el tema anterior ya se estudió el acceso a proveedores en hilos
separados, a través de loaders, para no bloquear la interfaz de usuario. Además, se puede
acceder por lotes (acceso batch) así como a través de Intents que delegarán en la propia
aplicación propietaria del proveedor de contenido quien, en general, proveerá una interfaz
mucho más adecuada para manejar los datos necesarios de dicho proveedor.
Los accesos por lotes son útiles cuando se pretende insertar, modificar o incluso borrar gran
cantidad de datos en un proveedor de contenido, en una o más tablas, en el contexto de una
transacción. Para realizar este tipo de operaciones, se invocará a
ContentResolver.applyBatch(), método que recibirá como parámetro un String que
determine la autoridad del proveedor (y no una URI), así como un ArrayList de
ContentProviderOperation, objetos que podrán operar sobre diferentes tablas del
proveedor.
Una aplicación puede ofrecer acceso a los datos de un proveedor sin necesidad de requerir
permisos especiales por parte del usuario a la hora de instalarla. Para ello, bastará con lanzar
un Intent contra el proveedor de contenido para mostrar su interfaz o para obtener los
resultados del Intent de vuelta en la aplicación.
Como resultado obtenido de vuelta en la aplicación, al lanzar el Intent, se recibirá una URI
con permisos temporales 11 (de lectura o escritura), de los datos solicitados, al recibir uno de
estos flags en el resultado: FLAG_GRANT_READ_URI_PERMISSION, o
FLAG_GRANT_WRITE_URI_PERMISSION.
Por ejemplo, para obtener contactos del proveedor de contactos sin necesidad de solicitar el
permiso READ_CONTACTS en el manifiesto de una aplicación, se podrá enviar un Intent con la
acción ACTION_PICK y el tipo MIME de los contactos
(ContactsContract.RawContacts.CONTACTS_ITEM_TYPE) a través del método de la
11
Estos permisos tendrán validez mientras la actividad no sea destruida.
Los proveedores de contenido pueden contener datos de tipo String, enteros (Integer, int),
enteros largos (long), decimales (float) y decimales largos (double). Además, también
pueden almacenar objetos de tipo BLOB 12 de hasta 64 KB, short y null. La clase Cursor
ofrece métodos accesores (tipo get()) para cada uno de estos tipos de datos.
Pese a que el tipo de dato de cada una de las columnas debe quedar especificado en la
documentación de cada proveedor de contenido, se podrá averiguar el mismo a través del
método Cursor.getType(int columnIndex) que devolverá las siguientes constantes:
FIELD_TYPE_NULL (= 0)
FIELD_TYPE_INTEGER (= 1)
FIELD_TYPE_FLOAT (= 2)
FIELD_TYPE_STRING (= 3)
FIELD_TYPE_BLOB (= 4)
Además de estos tipos básicos de datos, los proveedores pueden ofrecer tipos complejos de
datos, o tipos de datos MIME13. Para cada URI que defina un proveedor, existirá un tipo de
12
BLOB: Binary Large OBject
13
definidos originalmente para el intercambio a través de internet de todo tipo de archivos (audio, vídeo, texto
enriquecido, etc.)
Existen tipos MIME estándar (para contenidos multimedia) y tipos MIME propietarios. Un
proveedor de contenido podrá devolver cualquiera de ellos, o ambos.
El formato de los tipos MIME es tipo/subtipo, como por ejemplo, text/html. Las
peticiones de contenido a un proveedor que devuelva este tipo MIME en la URI accedida
devolverán texto con etiquetas HTML.
Los tipos MIME propietarios, o específicos del proveedor (“vendor-specific”), son más
complejos, aunque el tipo siempre será:
El subtipo será específico del proveedor y podrán estar basados en la autoridad (authority) del
proveedor, así como en los nombres de las tablas a los cuales se accede.
En el caso de los Contactos de Android, cuando se crea una fila para insertar un teléfono en su
correspondiente tabla, se establece el siguiente tipo MIME para dicha fila:
vnd.android.cursor.item/phone_v2.
En el caso de un proveedor de contenidos con otro tipo de datos más complejos, como por
ejemplo, un proveedor que contenga inventarios de elementos de instalaciones deportivas,
que tendría tablas para almacenar datos de elementos dentro de los gimnasios, de canchas
multipropósito, o de vestuarios, podría definir la autoridad com.instalacdeps.inventario y
las tablas Gimnasio, Cancha, Vestuario. Entonces, el tipo MIME de la URI
content://com.instalacdeps.inventario/Gimnasio
podría ser
vnd.android.cursor.dir/vnd.instalacdeps.gimnasio,
content://com.instalacdeps.inventario/Cancha/3
sería
vnd.android.cursor.item/vnd.instalacdeps.cancha.
Servicios
Los servicios, que se ejecutan en el hilo principal de la aplicación, son componentes diseñados
para realizar tareas en segundo plano que no requieran interfaz de usuario. Estas tareas
podrán ser, por ejemplo, lecturas y escrituras de archivos, interacciones con proveedores de
contenido, reproducción de música en segundo plano, etc.
Los servicios pueden ser iniciados por componentes de la aplicación o, incluso, por
componentes de otras aplicaciones a través del lanzamiento de Intents y, además, pueden
permitir que dichos componentes se les unan con el objetivo de permitir una comunicación
entre el servicio y el componente unido.
Un servicio podrá adoptar cualquiera de estas dos formas, o ambas. Bastará para ello que el
servicio implemente los métodos callback onStartCommand() y/o onBind() que serán
invocados por el sistema cuando se invoquen los métodos startService() y/o
bindService(), respectivamente, métodos que reciben de parámetro un Intent, entre otros.
Al igual que las actividades, los servicios han de ser declarados en el manifiesto de la aplicación
propietaria pudiendo ser definidos como privados de forma que solo sean accesibles desde la
propia aplicación.
Debido a que los servicios podrán realizar tareas de larga duración, en general deberán
implementar un segundo hilo de trabajo dentro de sí mismos para no bloquear el hilo principal
de la aplicación. Aun así, se deberá analizar adecuadamente la necesidad de crear un servicio
para realizar ciertas tareas. Si la tarea o proceso se ejecutará indefinidamente aunque la
actividad que la haya lanzado se cierre, será necesario crear un servicio. En cambio, si dicha
tarea o proceso únicamente ha de ejecutarse mientras la actividad esté viva, únicamente será
necesario crear un hilo en segundo plano 14.
Todos los servicios extenderán de la clase Service o de una subclase de esta. Será necesario
sobrescribir los métodos callback que el sistema invocará en distintos momentos del ciclo de
vida del servicio, y que además proveerán el mecanismo de unión al servicio, en caso de que
sea necesario.
Los servicios deberán ser declarados en el manifiesto de la aplicación, del mismo modo que las
actividades:
<application …>
<service android:name=".DownloaderService"></service>
</application>
Existen más atributos tales como los permisos requeridos para iniciar el servicio o el proceso
sobre el cuál se ejecutará, aunque el atributo android:name es el único atributo obligatorio (y
nunca deberá ser cambiado, una vez la aplicación sea publicada en Google Play).
Al igual que las actividades, y tal y como se estudiará detenidamente en un próximo tema, los
servicios también podrán definir filtros de Intents que permitan a otros componentes usar el
servicio utilizando Intents implícitos que serán pasados a cualquiera de los métodos del
contexto de la aplicación, startService() o bindService().
Si se desea hacer el servicio privado, no se declarará ningún filtro de Intent, de forma que
sólo se podrá invocar al servicio por medio de un Intent explícito (pasando el nombre de la
clase del servicio) y, además, se declarará el atributo android:exported="false" que, por
otro lado, permitirá la declaración y uso de filtros de Intents, accesibles solo dentro de la
propia aplicación.
Por último, los servicios podrán enviar notificaciones de eventos al usuario a través de Toast o
notificaciones en la barra de estado.
El ciclo de vida de un servicio es bastante más sencillo que el de una actividad, aunque deberá
prestarse mayor atención a cómo y cuándo se crea y destruye puesto que el servicio puede
ejecutarse en segundo plano sin que el usuario lo perciba.
Un servicio podrá seguir, básicamente, dos líneas en su ciclo de vida, según haya sido iniciado
vía startService() o se hayan unido componentes a él vía bindService(). Cualquiera de
14
Los hilos y procesos se tratarán en el próximo tema.
estos métodos creará el servicio (en el caso de que no haya sido ya creado 15). En el primer
caso, el servicio finalizará cuando él mismo invoque a su método stopSelf() o bien se
invoque desde el contexto a stopService(). En el segundo caso, el servicio finalizará cuando
todos los clientes que estén unidos se desvinculen del mismo.
El siguiente diagrama muestra los elementos principales del ciclo de vida de un servicio:
15
Es decir, la primera vez que se invoque cualquiera de esos métodos, se invocará al método callback onCreate()
del servicio. En sucesivas llamadas a dichos métodos, no se invocará a dicho método callback.
Se describen, a continuación, los métodos callback básicos del ciclo de vida de un servicio:
Existe otro importante método callback, onRebind(), que será invocado cuando nuevos
clientes se conecten al servicio, después de haberse notificado que todos los anteriores
clientes ya se habían desconectado, siempre y cuando onUnbind() devuelva true.
Creación de un servicio
onStartCommand() 16. Al tener su propio ciclo de vida, el servicio seguirá existiendo pese a que
el componente que lo haya invocado haya sido destruido, y finalizará cuando se invoque al
método del contexto stopService() o él mismo invoque a stopSelf().
Es necesario recordar que los servicios se ejecutan en el mismo proceso que el hilo principal de
la aplicación, por lo que si van a realizar tareas de larga duración deberán utilizar otro hilo
distinto que será instanciado dentro del servicio.
16
O bien en el método onStart(), en versiones menores o iguales a 1.6; este método está deprecado a partir de
2.0
Se puede controlar qué acción se realizará en caso de que el sistema elimine el servicio
abruptamente, a través del valor entero que devuelve el método onStartCommand(), gracias a
una de las siguientes constantes:
Como ya se ha explicado anteriormente, para iniciar un servicio desde una actividad, bastará
invocar al método startService() pasando de parámetro un Intent, y el propio sistema
invocará al método onStartCommand() pasando el Intent como parámetro. El método
startService() devuelve inmediatamente el control al hilo principal de la aplicación, sin
bloquearlo, mientras que el sistema invoca al método onStartCommand(), en caso de que el
servicio ya estuviese iniciado, o bien a onCreate(), si el servicio aún no está iniciado (y, a
continuación, a onStartCommand()).
Cualquier servicio puede ser invocado múltiples veces de forma concurrente a través de
startService() (y se deberán gestionar los Intents tal y como ya se ha explicado, bien
encolándolos al extender IntentService, o bien de forma manual lanzando múltiples hilos),
pero bastará una única llamada a stopService() o stopSelf() para detener el servicio.
Los servicios en primer plano son aquellos que el sistema no deberá detener en casos de
necesidades de memoria, ya que el usuario es consciente de su ejecución. Estos servicios
deben proveer una notificación en la barra de estado que estará ubicada en la zona de
notificaciones “ejecutándose” de modo que no pueda ser borrada hasta que el servicio haya
finalizado su tarea o haya sido eliminado del primer plano.
Uno de los ejemplos más típicos de un servicio en primer plano es la de una aplicación que
reproduzca música a través de un servicio, ya que el usuario estará escuchando lo que se esté
reproduciendo. La notificación en la barra de estado indicará qué canción se está
reproduciendo y no podrá ser borrada a no ser que el usuario pulse sobre la misma, active la
interfaz del reproductor, y detenga la canción. En ese momento, el servicio podrá ser enviado
a background o ser detenido, eliminándose así la notificación.
Para iniciar un servicio en primer plano, será necesario invocar al método startForeground()
pasando como parámetros un entero que identifique a la notificación, así como el objeto
Notification que será mostrado en la barra de estado. Para eliminar el servicio del primer
plano, bastará con invocar a stopForeground(), método que recibirá un booleano que
determinará si se elimina (true) o no (false) la notificación de la barra de estado. En
cualquier caso, al parar el servicio con stopService(), la notificación será eliminada.
17
Es importante adoptar estas buenas prácticas al gestionar el ciclo de vida de los servicios y solo parar los servicios
cuando hayan acabado de realizar su trabajo ya que, en otro caso, se estarán usando indebidamente los recursos
del sistema gastando, además, más batería. Siempre será necesario invocar a stopSelf() o stopSelfResult()
desde el momento en que se haya realizado una llamada a onStartCommand().
Servicios enlazados
Los servicios enlazados permiten que otros componentes se les unan de forma que puedan
interactuar entre ellos. Representan la parte “servidor” en un interfaz de tipo cliente-servidor.
Los clientes pueden enviarles peticiones, recibir repuestas e incluso realizar comunicación
entre procesos (IPC). En general, únicamente vivirán mientras tengan tareas que realizar
proveyendo información a ciertos componentes, a diferencia de los servicios comunes que, en
general viven indefinidamente.
Como todo servicio, los servicios enlazados extienden la clase Service y es necesario que
provean una implementación del método callback onBind() que devuelve una interfaz de tipo
IBinder la cual define los métodos que pueden usar los clientes para interactuar con el
servicio.
Para unirse a un servicio, el cliente deberá invocar al método del contexto de la aplicación,
bindService(), pasando como parámetro un Intent al servicio invocado, así como un
objeto ServiceConnection que se encargará de monitorizar la conexión con el servicio, y un
flag. El método, asíncrono, devolverá true si la unión al servicio ha sido satisfactoria y false si
la conexión no se puede realizar.
Tal y como ya se mencionó en una sección anterior, cuando el último cliente se desconecte del
servicio, el sistema lo destruirá, a no ser que dicho servicio hubiese sido iniciado previamente
con el método startService().
Cuando se crea un servicio enlazado, lo más importante es definir la interfaz de tipo IBinder
con la cual interactuarán los clientes. Para ello, se pueden utilizar dos técnicas distintas 18:
extender la clase Binder o bien utilizar un objeto de tipo Messenger.
Extensión de Binder
Se creará una interfaz tipo IBinder que extienda de Binder y que sea devuelta en el método
onBind(), en aquellos casos en los que el servicio sea privado (usado únicamente dentro de la
18
Existe una tercera técnica que no será estudiada en el curso, basada en la implementación de objetos que se
reducen a primitivas utilizando el Lenguaje de Definición de Interfaces de Android (AIDL)
aplicación) y se ejecute en el mismo proceso que el cliente y actúe como una tarea en segundo
plano (worker), lo cual es bastante común. Esta técnica no deberá ser usada si el servicio ha de
ejecutarse en otro hilo o si puede ser usado por otras aplicaciones.
Para configurar un servicio de este modo, se deberá crear una instancia de Binder que, o bien
contenga los métodos públicos que el cliente puede invocar, o bien devuelva una instancia del
servicio o de otra clase que albergue el servicio, el cual contendrá los métodos públicos que el
cliente puede invocar. A continuación, dicha instancia del Binder será devuelta al cliente a
través del método callback onBind(). La interfaz será recibida en el cliente a través del
método callback onServiceConnected().
@Override
public IBinder onBind(Intent arg0) {
return mMiBinder;
}
AdivinaNumeroBinder, que implementa los métodos a través de los cuales los clientes podrán
extraer información del servicio. En el método onBind() se devolverá la interfaz de tipo
IBinder de AdivinaNumeroBinder.
Para unirse a dicho servicio, una actividad cliente deberá crear un Intent al servicio e invocar
a bindService(), pasando como parámetro dicho Intent, un flag, y un objeto tipo
ServiceConnection. En este objeto se deberán implementar los métodos
onServiceConnected() y onServiceDisconnected(), para monitorizar la conexión con el
servicio. Además, el primero de los métodos recibe la interfaz IBinder a través de la cual el
cliente se comunicará con el servicio.
AdivinaNumeroBinder mBinder;
boolean mServicioUnido = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Se invoca a AdivinaNumeroService vía Intent para unirse a
// él.
Intent intent = new Intent(this, AdivinaNumeroService.class);
bindService(intent, mConexion, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Desconexión del servicio
if (mServicioUnido) {
unbindService(mConexion);
mServicioUnido = false;
}
}
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// Se obtiene el interfaz del servicio, una vez se ha
// unido a él.
mBinder = (AdivinaNumeroBinder) service;
mServicioUnido = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mServicioUnido = false;
}
};
}
Para utilizar los métodos que hace disponibles la interfaz IBinder de este servicio, se puede
implementar un método que obtenga números aleatorios generados en el servicio:
if (mServicioUnido) {
Uso de Messenger
Cuando el servicio se ejecuta en otro proceso distinto al proceso del cliente que lo invoca, y se
quiere comunicar con él, se podrá utilizar un Messenger que provea la interfaz con el servicio y
que permite comunicación entre procesos (IPC). Para utilizar esta técnica, que permite que el
cliente pueda enviar mensajes al servicio, se deberán realizar los siguientes pasos:
1. El servicio implementará un Handler que recibirá las peticiones del cliente a través de
sus métodos callback.
2. El Handler se pasará como referencia al constructor que creará un objeto de tipo
Messenger (en el servicio).
3. El Messenger creará la interfaz tipo IBinder que devolverá el método onBind().
4. Los clientes utilizarán la interfaz IBinder para instanciar el Messenger 19 (que a su vez
contiene una referencia al Handler), que será utilizado por el cliente para enviar
objetos de tipo Message al servicio.
5. El servicio recibirá los objetos Message en el método handleMessage() del Handler.
19
Existe por lo tanto dos objetos de tipo Messenger (dos mensajeros): uno en el servicio y otro en el cliente.
De esta forma, el cliente no accede a métodos de la interfaz IBinder si no que envía mensajes
que serán gestionados por el servicio en el objeto Handler.
En caso de querer recibir respuestas en el cliente desde el servicio, será necesario crear un
Messenger en el cliente. Cuando el cliente reciba la llamada a su servicio callback
onServiceConnected(), deberá enviar un mensaje al servicio que incluya el objeto
Messenger del cliente como parámetro replyTo del método send(). De esta forma, el
servicio puede almacenar una referencia del Messenger del cliente, que utilizará para enviarle
mensajes de respuesta, ante sucesivas peticiones del cliente.
Tanto el cliente como el servicio gestionarán los mensajes recibidos vía Messenger en el
método handleMessage() de la clase interna tipo Handler.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button btnAdivinar = (Button) findViewById(R.id.btnAdivinar);
btnAdivinar.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
adivinar(v);
}
});
// Se conecta automáticamente el servicio según se inicia la
// actividad
conectarServicio(null);
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mServicio = null;
mServicioUnido = false;
}
};
}
}
}
/**
* Mensajero que se publica para el servicio, para enviar mensajes
* al Handler del Cliente.
*/
final Messenger mMessenger = new Messenger(new HandlerCliente());
/**
* Handler que gestionará los mensajes provenientes de los
* clientes.
*/
Comentarios finales
En caso de que se rompa la conexión con el servicio, la única excepción que enviarán los
métodos remotos (en caso de usar un Binder) será DeadObjectException, la cual deberá ser
gestionada.
No se deben utilizar los métodos onResume() y onPause() para la unión y desconexión del
servicio, puesto que dichos métodos han de permanecer lo más ligeros posible.