En esta página, se describe cómo registrar y descubrir servicios, y cómo enviar datos a un servicio llamando a métodos definidos en interfaces en archivos .hal
.
Registrar servicios
Los servidores de interfaz HIDL (objetos que implementan la interfaz) se pueden registrar como servicios nombrados. El nombre registrado no tiene que estar relacionado con el nombre de la interfaz ni del paquete. Si no se especifica un nombre, se usa el nombre "default", que se debe usar para los HAL que no necesitan registrar dos implementaciones de la misma interfaz. Por ejemplo, la llamada a C++ para el registro de servicios definida en cada interfaz es la siguiente:
status_t status = myFoo->registerAsService(); status_t anotherStatus = anotherFoo->registerAsService("another_foo_service"); // if needed
La versión de una interfaz HIDL se incluye en la interfaz. Se asocia automáticamente con el registro del servicio y se puede recuperar a través de una llamada de método (android::hardware::IInterface::getInterfaceVersion()
) en cada interfaz HIDL. No es necesario registrar los objetos del servidor, y se pueden pasar a través de parámetros de método HIDL a otro proceso que realice llamadas al método HIDL en el servidor.
Descubre servicios
Las solicitudes por código de cliente se realizan para una interfaz determinada por nombre y por versión, y llaman a getService
en la clase de HAL deseada:
// C++ sp<V1_1::IFooService> service = V1_1::IFooService::getService(); sp<V1_1::IFooService> alternateService = V1_1::IFooService::getService("another_foo_service"); // Java V1_1.IFooService service = V1_1.IFooService.getService(true /* retry */); V1_1.IFooService alternateService = V1_1.IFooService.getService("another", true /* retry */);
Cada versión de una interfaz HIDL se considera una interfaz independiente. Por lo tanto,
IFooService
versión 1.1 y IFooService
versión 2.2
se pueden registrar como "foo_service" y
getService("foo_service")
en cualquier interfaz obtiene el servicio registrado
para esa interfaz. Por este motivo, en la mayoría de los casos, no es necesario proporcionar ningún parámetro de nombre para el registro o el descubrimiento (es decir, el nombre "predeterminado").
El objeto de interfaz de proveedor también desempeña un papel en el método de transporte de la interfaz que se muestra. Para una interfaz IFoo
en el paquete android.hardware.foo@1.0
, la interfaz que muestra IFoo::getService
siempre usa el método de transporte declarado para android.hardware.foo
en el manifiesto del dispositivo si existe la entrada. Si el método de transporte no está disponible, se muestra nullptr.
En algunos casos, es posible que debas continuar de inmediato, incluso sin obtener el servicio. Esto puede suceder (por ejemplo) cuando un cliente quiere administrar las notificaciones del servicio por sí mismo o en un programa de diagnóstico (como atrace
) que necesita obtener todos los hwservices y recuperarlos. En este caso, se proporcionan APIs adicionales, como tryGetService
en C++ o getService("instance-name", false)
en Java. La API heredada getService
proporcionada en Java también se debe usar con notificaciones de servicio. El uso de esta API no evita la condición de carrera en la que un servidor se registra después de que el cliente lo solicita con una de estas APIs sin reintentos.
Notificaciones de falla del servicio
Los clientes que quieran recibir una notificación cuando un servicio deje de funcionar pueden recibir notificaciones de falla que envía el framework. Para recibir notificaciones, el cliente debe hacer lo siguiente:
- Crea una subclase de la clase o interfaz HIDL
hidl_death_recipient
(en código C++, no en HIDL). - Anula su método
serviceDied()
. - Crea una instancia de un objeto de la subclase
hidl_death_recipient
. - Llama al método
linkToDeath()
en el servicio que deseas supervisar y pasa el objeto de interfaz deIDeathRecipient
. Ten en cuenta que este método no toma la propiedad del destinatario de error ni del proxy al que se llama.
Ejemplo de pseudocódigo (C++ y Java son similares):
class IMyDeathReceiver : hidl_death_recipient { virtual void serviceDied(uint64_t cookie, wp<IBase>& service) override { log("RIP service %d!", cookie); // Cookie should be 42 } }; .... IMyDeathReceiver deathReceiver = new IMyDeathReceiver(); m_importantService->linkToDeath(deathReceiver, 42);
El mismo destinatario de fallecimiento se puede registrar en varios servicios diferentes.
Transferencia de datos
Para enviar datos a un servicio, llama a los métodos definidos en las interfaces de los archivos .hal
. Existen dos tipos de métodos:
- Los métodos de bloqueo esperan hasta que el servidor genere un resultado.
- Los métodos unidireccionales envían datos en una sola dirección y no se bloquean. Si la cantidad de datos en tránsito en las llamadas a RPC supera los límites de implementación, las llamadas pueden bloquearse o mostrar una indicación de error (aún no se determina el comportamiento).
Un método que no muestra un valor, pero que no se declara como oneway
, sigue bloqueando.
A todos los métodos declarados en una interfaz HIDL se los llama en una sola dirección, ya sea desde el HAL o hacia el HAL. La interfaz no especifica en qué dirección se llama. Las arquitecturas que necesitan que las llamadas se originen en el HAL deben proporcionar dos (o más) interfaces en el paquete HAL y entregar la interfaz adecuada desde cada proceso. Las palabras cliente y servidor se usan en relación con la dirección de llamada de la interfaz (es decir, el HAL puede ser un servidor de una interfaz y un cliente de otra).
Devoluciones de llamada
La palabra devolución de llamada hace referencia a dos conceptos diferentes, que se distinguen por la devolución de llamada síncrona y la devolución de llamada asíncrona.
Las devoluciones de llamada síncronas se usan en algunos métodos de HIDL que muestran datos. Un método HIDL que muestra más de un valor (o muestra un valor de tipo no primitivo) muestra sus resultados a través de una función de devolución de llamada. Si solo se muestra un valor y es un tipo primitivo, no se usa una devolución de llamada y el valor se muestra desde el método. El servidor implementa los métodos HIDL y el cliente implementa las devoluciones de llamada.
Las devoluciones de llamada asíncronas permiten que el servidor de una interfaz HIDL origine llamadas. Para ello, se pasa una instancia de una segunda interfaz a través de la primera. El cliente de la primera interfaz debe actuar como el servidor de la segunda. El servidor de la primera interfaz puede llamar a métodos en el objeto de la segunda interfaz. Por ejemplo, una implementación de HAL puede enviar información de forma asíncrona al proceso que la usa llamando a métodos en un objeto de interfaz creado y publicado por ese proceso. Los métodos en las interfaces que se usan para la devolución de llamada asíncrona pueden bloquearse (y pueden mostrar valores al llamador) o ser oneway
. Para ver un ejemplo, consulta "Llamadas de devolución de llamada asíncronas" en HIDL C++.
Para simplificar la propiedad de la memoria, las llamadas a métodos y las devoluciones de llamada solo toman parámetros in
y no admiten parámetros out
ni inout
.
Límites por transacción
No se imponen límites por transacción a la cantidad de datos que se envían en los métodos y las devoluciones de llamada de HIDL. Sin embargo, las llamadas que superan los 4 KB por transacción se consideran excesivas. Si se observa esto, se recomienda volver a crear la arquitectura de la interfaz HIDL proporcionada. Otra limitación son los recursos disponibles para la infraestructura de HIDL para controlar varias transacciones simultáneas. Varias transacciones pueden estar en curso de forma simultánea debido a varios subprocesos o procesos que envían llamadas a un proceso o varias llamadas a oneway
que el proceso receptor no controla con rapidez. El espacio total máximo disponible para todas las transacciones simultáneas es de 1 MB de forma predeterminada.
En una interfaz bien diseñada, no debería ocurrir que se superen estas limitaciones de recursos. Si esto sucede, la llamada que las superó puede bloquearse hasta que los recursos estén disponibles o indicar un error de transporte. Para facilitar la depuración, se registra cada ocurrencia de superación de los límites por transacción o desbordamiento de recursos de implementación de HIDL por transacciones en curso agregadas.
Implementaciones de métodos
HIDL genera archivos de encabezado que declaran los tipos, métodos y callbacks necesarios en el lenguaje de destino (C++ o Java). El prototipo de métodos y devoluciones de llamada definidos por HIDL es el mismo para el código del cliente y del servidor. El sistema HIDL proporciona implementaciones de proxy de los métodos en el lado del llamador que organizan los datos para el transporte de IPC y el código de stub en el lado del llamado que pasa los datos a las implementaciones de los métodos por parte del desarrollador.
El llamador de una función (método HIDL o devolución de llamada) es propietario de las estructuras de datos que se pasan a la función y retiene la propiedad después de la llamada. En todos los casos, el llamado no necesita liberar el almacenamiento.
- En C++, los datos pueden ser de solo lectura (los intentos de escribir en ellos pueden causar una falla de segmentación) y son válidos durante la llamada. El cliente puede hacer una copia profunda de los datos para propagarlos más allá de la llamada.
- En Java, el código recibe una copia local de los datos (un objeto Java normal), que puede conservar y modificar, o permitir que se recopile la basura.
Transferencia de datos que no son de RPC
HIDL tiene dos formas de transferir datos sin usar una llamada a RPC: memoria compartida y una cola de mensajes rápida (FMQ), ambas compatibles solo con C++.
- Memoria compartida: El tipo HIDL integrado
memory
se usa para pasar un objeto que representa la memoria compartida que se asignó. Se puede usar en un proceso receptor para asignar la memoria compartida. - Cola de mensajes rápidos (FMQ). HIDL proporciona un tipo de fila de mensajes con plantillas que implementa el paso de mensajes sin espera. No usa el kernel ni el programador en modo de transferencia o vinculación (la comunicación entre dispositivos no tiene estas propiedades). Por lo general, el sistema HAL configura su extremo de la cola y crea un objeto que se puede pasar a través de RPC mediante un parámetro de tipo HIDL integrado
MQDescriptorSync
oMQDescriptorUnsync
. El proceso receptor puede usar este objeto para configurar el otro extremo de la cola.- Las filas de Sync no pueden desbordarse y solo pueden tener un lector.
- Las colas no sincronizadas pueden desbordarse y tener muchos lectores, cada uno de los cuales debe leer los datos a tiempo o perderlos.
Para obtener más detalles sobre la FMQ, consulta Cola de mensajes rápidos (FMQ).