Interfaces

Chaque interface définie dans un package HIDL possède sa propre classe C++ générée automatiquement dans l'espace de noms de son package. Les clients et les serveurs gèrent les interfaces de différentes manières :

  • Les serveurs implémentent des interfaces.
  • Les clients appellent des méthodes sur les interfaces.

Les interfaces peuvent être soit enregistrées par leur nom par le serveur, soit transmises en tant que paramètres aux méthodes définies par HIDL. Par exemple, le code-cadre peut servir à une interface pour recevoir des messages asynchrones de la HAL et transmettre cette interface directement à la HAL sans l'enregistrer.

Implémentation du serveur

Un serveur implémentant l'interface IFoo doit inclure le fichier d'en-tête IFoo généré automatiquement :

#include <android/hardware/samples/1.0/IFoo.h>

L'en-tête est automatiquement exporté par la bibliothèque partagée de l'interface IFoo pour établir un lien. Exemple IFoo.hal :

// IFoo.hal
interface IFoo {
    someMethod() generates (vec<uint32_t>);
    ...
}

Exemple de squelette pour une implémentation serveur de l'interface IFoo :

// From the IFoo.h header
using android::hardware::samples::V1_0::IFoo;

class FooImpl : public IFoo {
    Return<void> someMethod(foo my_foo, someMethod_cb _cb) {
        vec<uint32_t> return_data;
        // Compute return_data
        _cb(return_data);
        return Void();
    }
    ...
};

Pour rendre accessible à un client l'implémentation d'une interface serveur, vous pouvez :

  1. Enregistrez l'implémentation de l'interface auprès du hwservicemanager (voir les détails ci-dessous),

    OU

  2. Transmettez l'implémentation de l'interface comme argument d'une méthode d'interface (pour plus de détails, voir Rappels asynchrones ).

Lors de l'enregistrement de l'implémentation de l'interface, le processus hwservicemanager assure le suivi des interfaces HIDL enregistrées exécutées sur le périphérique par nom et version. Les serveurs peuvent enregistrer une implémentation d'interface HIDL par nom et les clients peuvent demander des implémentations de service par nom et version. Ce processus sert l'interface HIDL android.hidl.manager@1.0::IServiceManager .

Chaque fichier d'en-tête d'interface HIDL généré automatiquement (tel que IFoo.h ) possède une méthode registerAsService() qui peut être utilisée pour enregistrer l'implémentation de l'interface auprès de hwservicemanager . Le seul argument obligatoire est le nom des implémentations de l'interface, car les clients utiliseront ce nom pour récupérer l'interface du hwservicemanager ultérieurement :

::android::sp<IFoo> myFoo = new FooImpl();
::android::sp<IFoo> mySecondFoo = new FooAnotherImpl();
status_t status = myFoo->registerAsService();
status_t anotherStatus = mySecondFoo->registerAsService("another_foo");

Le hwservicemanager traite la combinaison de [package@version::interface, instance_name] comme unique pour permettre à différentes interfaces (ou différentes versions de la même interface) de s'enregistrer avec des noms d'instance identiques sans conflits. Si vous appelez registerAsService() avec exactement la même version de package, la même interface et le même nom d'instance, hwservicemanager supprime sa référence au service précédemment enregistré et utilise le nouveau.

Implémentation client

Tout comme le serveur, un client doit #include chaque interface à laquelle il fait référence :

#include <android/hardware/samples/1.0/IFoo.h>

Un client peut obtenir une interface de deux manières :

  • Via I<InterfaceName>::getService (via le hwservicemanager )
  • Via une méthode d'interface

Chaque fichier d'en-tête d'interface généré automatiquement possède une méthode getService statique qui peut être utilisée pour récupérer une instance de service à partir du hwservicemanager :

// getService will return nullptr if the service can't be found
sp<IFoo> myFoo = IFoo::getService();
sp<IFoo> myAlternateFoo = IFoo::getService("another_foo");

Le client dispose désormais d'une interface IFoo et peut y appeler des méthodes comme s'il s'agissait d'une implémentation de classe locale. En réalité, l'implémentation peut s'exécuter dans le même processus, dans un processus différent ou même sur un autre appareil (avec HAL remoting). Étant donné que le client a appelé getService sur un objet IFoo inclus dans la version 1.0 du package, hwservicemanager renvoie une implémentation de serveur uniquement si cette implémentation est compatible avec les clients 1.0 . En pratique, cela signifie uniquement les implémentations de serveur avec la version 1.n (la version x.(y+1) d'une interface doit étendre (hériter de) xy ).

De plus, la méthode castFrom est fournie pour effectuer un cast entre différentes interfaces. Cette méthode fonctionne en effectuant un appel IPC à l'interface distante pour s'assurer que le type sous-jacent est le même que le type demandé. Si le type demandé n'est pas disponible, nullptr est renvoyé.

sp<V1_0::IFoo> foo1_0 = V1_0::IFoo::getService();
sp<V1_1::IFoo> foo1_1 = V1_1::IFoo::castFrom(foo1_0);

Rappels asynchrones

De nombreuses implémentations HAL existantes communiquent avec du matériel asynchrone, ce qui signifie qu'elles ont besoin d'un moyen asynchrone pour informer les clients des nouveaux événements survenus. Une interface HIDL peut être utilisée comme rappel asynchrone car les fonctions d'interface HIDL peuvent prendre des objets d'interface HIDL comme paramètres.

Exemple de fichier d'interface IFooCallback.hal :

package android.hardware.samples@1.0;
interface IFooCallback {
    sendEvent(uint32_t event_id);
    sendData(vec<uint8_t> data);
}

Exemple de nouvelle méthode dans IFoo qui prend un paramètre IFooCallback :

package android.hardware.samples@1.0;
interface IFoo {
    struct Foo {
       int64_t someValue;
       handle myHandle;
    };

    someMethod(Foo foo) generates (int32_t ret);
    anotherMethod() generates (vec<uint32_t>);
    registerCallback(IFooCallback callback);
};

Le client utilisant l'interface IFoo est le serveur de l'interface IFooCallback ; il fournit une implémentation de IFooCallback :

class FooCallback : public IFooCallback {
    Return<void> sendEvent(uint32_t event_id) {
        // process the event from the HAL
    }
    Return<void> sendData(const hidl_vec<uint8_t>& data) {
        // process data from the HAL
    }
};

Il peut également simplement transmettre cela sur une instance existante de l'interface IFoo :

sp<IFooCallback> myFooCallback = new FooCallback();
myFoo.registerCallback(myFooCallback);

Le serveur implémentant IFoo reçoit cela en tant qu'objet sp<IFooCallback> . Il peut stocker le rappel et rappeler le client chaque fois qu'il souhaite utiliser cette interface.

Destinataires du décès

Comme les implémentations de services peuvent s'exécuter dans un processus différent, il peut arriver que le processus implémentant une interface meure alors que le client reste en vie. Tout appel sur un objet d'interface hébergé dans un processus décédé échouera avec une erreur de transport ( isOK() renverra false). La seule façon de récupérer d'un tel échec est de demander une nouvelle instance du service en appelant I<InterfaceName>::getService() . Cela ne fonctionne que si le processus qui s'est écrasé a redémarré et réenregistré ses services auprès du servicemanager (ce qui est généralement vrai pour les implémentations HAL).

Au lieu de gérer cela de manière réactive, les clients d'une interface peuvent également enregistrer un destinataire décédé pour recevoir une notification en cas de décès d'un service. Pour s'inscrire à de telles notifications sur une interface IFoo récupérée, un client peut procéder comme suit :

foo->linkToDeath(recipient, 1481 /* cookie */);

Le paramètre recipient doit être une implémentation de l'interface android::hardware::hidl_death_recipient fournie par HIDL, qui contient une seule méthode serviceDied() qui sera appelée à partir d'un thread dans le pool de threads RPC lorsque le processus hébergeant l'interface meurt :

class MyDeathRecipient : public android::hardware::hidl_death_recipient {
    virtual void serviceDied(uint64_t cookie, const android::wp<::android::hidl::base::V1_0::IBase>& who) {
       // Deal with the fact that the service died
    }
}

Le paramètre cookie contient le cookie qui a été transmis avec linkToDeath() , tandis que le paramètre who contient un pointeur faible vers l'objet représentant le service dans le client. Avec l'exemple d'appel donné ci-dessus, cookie est égal à 1481 et who est égal à foo .

Il est également possible de désinscrire un destinataire de décès après l'avoir enregistré :

foo->unlinkToDeath(recipient);