Cette page explique comment enregistrer et découvrir des services, et comment envoyer des données à un service en appelant des méthodes définies dans des interfaces dans des fichiers .hal
.
Enregistrer les services
Les serveurs d'interface HIDL (objets implémentant l'interface) peuvent être enregistrés en tant que services nommés. Le nom enregistré n'a pas besoin d'être lié au nom de l'interface ou du package. Si aucun nom n'est spécifié, le nom "default" est utilisé. Il doit être utilisé pour les HAL qui n'ont pas besoin d'enregistrer deux implémentations de la même interface. Par exemple, l'appel C++ pour l'enregistrement de service défini dans chaque interface est le suivant:
status_t status = myFoo->registerAsService(); status_t anotherStatus = anotherFoo->registerAsService("another_foo_service"); // if needed
La version d'une interface HIDL est incluse dans l'interface elle-même. Il est automatiquement associé à l'enregistrement de service et peut être récupéré via un appel de méthode (android::hardware::IInterface::getInterfaceVersion()
) sur chaque interface HIDL. Les objets serveur n'ont pas besoin d'être enregistrés et peuvent être transmis via des paramètres de méthode HIDL à un autre processus qui effectue des appels de méthode HIDL sur le serveur.
Découvrir des services
Les requêtes par code client sont effectuées pour une interface donnée par nom et par version, en appelant getService
sur la classe HAL souhaitée:
// 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 */);
Chaque version d'une interface HIDL est traitée comme une interface distincte. Par conséquent, la version 1.1 de IFooService
et la version 2.2 de IFooService
peuvent toutes deux être enregistrées en tant que "foo_service", et getService("foo_service")
sur l'une ou l'autre interface obtient le service enregistré pour cette interface. C'est pourquoi, dans la plupart des cas, aucun paramètre de nom n'est nécessaire pour l'enregistrement ou la découverte (nom "par défaut").
L'objet Interface du fournisseur joue également un rôle dans la méthode de transport de l'interface renvoyée. Pour une interface IFoo
dans le package android.hardware.foo@1.0
, l'interface renvoyée par IFoo::getService
utilise toujours la méthode de transport déclarée pour android.hardware.foo
dans le fichier manifeste de l'appareil si l'entrée existe. Si la méthode de transport n'est pas disponible, nullptr est renvoyé.
Dans certains cas, il peut être nécessaire de continuer immédiatement, même sans obtenir le service. Cela peut se produire (par exemple) lorsqu'un client souhaite gérer lui-même les notifications de service ou dans un programme de diagnostic (tel que atrace
) qui doit obtenir tous les services matériels et les récupérer. Dans ce cas, des API supplémentaires sont fournies, telles que tryGetService
en C++ ou getService("instance-name", false)
en Java. L'ancienne API getService
fournie en Java doit également être utilisée avec les notifications de service. L'utilisation de cette API n'évite pas la condition de course dans laquelle un serveur s'enregistre après que le client l'a demandé avec l'une de ces API sans nouvelle tentative.
Notifications de fin de service
Les clients qui souhaitent être avertis lorsqu'un service cesse de fonctionner peuvent recevoir des notifications de fin de service envoyées par le framework. Pour recevoir des notifications, le client doit:
- Sous-classe de la classe/interface HIDL
hidl_death_recipient
(en code C++, pas en HIDL). - Remplacez sa méthode
serviceDied()
. - Instanciez un objet de la sous-classe
hidl_death_recipient
. - Appelez la méthode
linkToDeath()
sur le service à surveiller, en transmettant l'objet d'interface deIDeathRecipient
. Notez que cette méthode ne prend pas la propriété du destinataire de la notification de décès ni du proxy sur lequel elle est appelée.
Exemple de pseudo-code (C++ et Java sont similaires):
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);
Un même bénéficiaire peut être enregistré dans plusieurs services différents.
Transfert des données
Vous pouvez envoyer des données à un service en appelant les méthodes définies dans les interfaces des fichiers .hal
. Il existe deux types de méthodes:
- Les méthodes bloquantes attendent que le serveur ait généré un résultat.
- Les méthodes à sens unique n'envoient des données que dans un seul sens et ne bloquent pas. Si la quantité de données en cours d'exécution dans les appels RPC dépasse les limites d'implémentation, les appels peuvent être bloqués ou renvoyer une indication d'erreur (le comportement n'est pas encore déterminé).
Une méthode qui ne renvoie pas de valeur, mais qui n'est pas déclarée comme oneway
, est toujours bloquante.
Toutes les méthodes déclarées dans une interface HIDL sont appelées dans une seule direction, soit à partir du HAL, soit dans le HAL. L'interface ne spécifie pas dans quelle direction elle est appelée. Les architectures qui nécessitent des appels provenant du HAL doivent fournir deux interfaces (ou plus) dans le package HAL et servir l'interface appropriée à partir de chaque processus. Les termes client et serveur sont utilisés par rapport à la direction d'appel de l'interface (c'est-à-dire que le HAL peut être un serveur d'une interface et un client d'une autre interface).
Rappels
Le mot rappel fait référence à deux concepts différents, distingués par le rappel synchrone et le rappel asynchrone.
Les rappels synchrones sont utilisés dans certaines méthodes HIDL qui renvoient des données. Une méthode HIDL qui renvoie plusieurs valeurs (ou une valeur de type non primitif) renvoie ses résultats via une fonction de rappel. Si une seule valeur est renvoyée et qu'il s'agit d'un type primitif, aucun rappel n'est utilisé et la valeur est renvoyée à partir de la méthode. Le serveur implémente les méthodes HIDL et le client implémente les rappels.
Les rappels asynchrones permettent au serveur d'une interface HIDL d'effectuer des appels. Pour ce faire, transmettez une instance d'une deuxième interface via la première interface. Le client de la première interface doit agir en tant que serveur de la seconde. Le serveur de la première interface peut appeler des méthodes sur l'objet de la deuxième interface. Par exemple, une implémentation HAL peut renvoyer des informations de manière asynchrone au processus qui l'utilise en appelant des méthodes sur un objet d'interface créé et géré par ce processus. Les méthodes des interfaces utilisées pour le rappel asynchrone peuvent être bloquantes (et peuvent renvoyer des valeurs à l'appelant) ou oneway
. Pour obtenir un exemple, consultez la section "Rappels asynchrones" dans HIDL C++.
Pour simplifier la propriété de la mémoire, les appels de méthode et les rappels n'acceptent que des paramètres in
et ne prennent pas en charge les paramètres out
ou inout
.
Limites par transaction
Les limites par transaction ne sont pas imposées à la quantité de données envoyées dans les méthodes et les rappels HIDL. Toutefois, les appels dépassant 4 ko par transaction sont considérés comme excessifs. Dans ce cas, nous vous recommandons de réorganiser l'interface HIDL donnée. Une autre limite concerne les ressources disponibles pour l'infrastructure HIDL afin de gérer plusieurs transactions simultanées. Plusieurs transactions peuvent être en cours de traitement simultanément en raison de plusieurs threads ou processus qui envoient des appels à un processus ou à plusieurs appels oneway
qui ne sont pas traités rapidement par le processus destinataire. L'espace total maximal disponible pour toutes les transactions simultanées est de 1 Mo par défaut.
Dans une interface bien conçue, le dépassement de ces limites de ressources ne devrait pas se produire. Dans le cas contraire, l'appel qui les a dépassées peut se bloquer jusqu'à ce que les ressources soient disponibles ou signaler une erreur de transport. Chaque cas de dépassement des limites par transaction ou de débordement des ressources d'implémentation HIDL par transactions en cours d'exécution est consigné pour faciliter le débogage.
Implémentations de méthode
HIDL génère des fichiers d'en-tête déclarant les types, méthodes et rappels nécessaires dans la langue cible (C++ ou Java). Le prototype des méthodes et des rappels définis par HIDL est le même pour le code client et le code serveur. Le système HIDL fournit des implémentations de proxy des méthodes côté appelant qui organisent les données pour le transport IPC, ainsi qu'un code de stub côté appelé qui transmet les données aux implémentations des méthodes par le développeur.
L'appelant d'une fonction (méthode HIDL ou rappel) est propriétaire des structures de données transmises à la fonction et conserve la propriété après l'appel. Dans tous les cas, l'appelé n'a pas besoin de libérer le stockage.
- En C++, les données peuvent être en lecture seule (les tentatives d'écriture peuvent entraîner une erreur de segmentation) et sont valides pendant toute la durée de l'appel. Le client peut effectuer une copie profonde des données pour les propager au-delà de l'appel.
- En Java, le code reçoit une copie locale des données (un objet Java normal), qu'il peut conserver et modifier ou autoriser à être collectées.
Transfert de données non RPC
HIDL propose deux méthodes de transfert de données sans utiliser d'appel RPC: la mémoire partagée et une file d'attente de messages rapide (FMQ), toutes deux compatibles uniquement avec C++.
- Mémoire partagée Le type HIDL intégré
memory
permet de transmettre un objet représentant une mémoire partagée qui a été allouée. Peut être utilisé dans un processus de réception pour mapper la mémoire partagée. - Fast Message Queue (FMQ) HIDL fournit un type de file d'attente de messages avec modèle qui implémente la transmission de messages sans attente. Il n'utilise pas le noyau ni le planificateur en mode passthrough ou binderized (la communication inter-appareils ne possède pas ces propriétés). En règle générale, le HAL configure son extrémité de la file d'attente, créant un objet pouvant être transmis via RPC via un paramètre de type HIDL intégré
MQDescriptorSync
ouMQDescriptorUnsync
. Cet objet peut être utilisé par le processus destinataire pour configurer l'autre extrémité de la file d'attente.- Les files d'attente de synchronisation ne doivent pas déborder et ne peuvent avoir qu'un seul lecteur.
- Les files d'attente Unsync peuvent déborder et peuvent avoir de nombreux lecteurs, chacun devant lire les données à temps ou les perdre.
Pour en savoir plus sur la file d'attente de messages rapide, consultez la section File d'attente de messages rapide (FMQ).