Интерфейсы

Каждый интерфейс, определенный в пакете HIDL, имеет собственный автоматически сгенерированный класс C++ в пространстве имен своего пакета. Клиенты и серверы по-разному работают с интерфейсами:

  • Серверы реализуют интерфейсы.
  • Клиенты вызывают методы в интерфейсах.

Интерфейсы могут быть зарегистрированы сервером по имени или переданы в качестве параметров в методы, определенные в HIDL. Например, код фреймворка может предоставить интерфейс для получения асинхронных сообщений от HAL и передать этот интерфейс непосредственно в HAL без регистрации.

Реализация на стороне сервера

Сервер, реализующий интерфейс IFoo, должен включать автоматически созданный заголовочный файл IFoo:

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

Заголовок автоматически экспортируется общей библиотекой интерфейса IFoo для связывания. Пример IFoo.hal:

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

Пример скелета для реализации интерфейса 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();
    }
    ...
};

Чтобы сделать реализацию интерфейса сервера доступной для клиента, можно:

  1. Зарегистрируйте реализацию интерфейса с помощью hwservicemanager (подробности ниже),

    ИЛИ

  2. Передайте реализацию интерфейса в качестве аргумента метода интерфейса (подробнее об этом читайте в разделе Асинхронные обратные вызовы).

При регистрации реализации интерфейса процесс hwservicemanager отслеживает зарегистрированные интерфейсы HIDL, работающие на устройстве, по названию и версии. Серверы могут регистрировать реализацию интерфейса HIDL по имени, а клиенты могут запрашивать реализации сервиса по имени и версии. Этот процесс обслуживает интерфейс HIDL android.hidl.manager@1.0::IServiceManager.

У каждого автоматически созданного файла заголовка интерфейса HIDL (например, IFoo.h) есть метод registerAsService(), который можно использовать для регистрации реализации интерфейса в hwservicemanager. Единственный обязательный аргумент – это название реализации интерфейса, поскольку клиенты используют его для получения интерфейса из hwservicemanager:

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

hwservicemanager обрабатывает комбинацию [package@version::interface, instance_name] как уникальную, чтобы разные интерфейсы (или разные версии одного интерфейса) могли регистрироваться с одинаковыми названиями экземпляров без конфликтов. Если вы вызываете функцию registerAsService() с теми же версией пакета, интерфейсом и названием экземпляра, hwservicemanager удаляет ссылку на ранее зарегистрированный сервис и использует новый.

Реализация на стороне клиента

Как и сервер, клиент должен #include каждый интерфейс, на который он ссылается:

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

Клиент может получить интерфейс двумя способами:

  • Через I<InterfaceName>::getService (hwservicemanager)
  • через интерфейс;

Каждый автоматически созданный заголовочный файл интерфейса содержит статический метод getService, который можно использовать для получения экземпляра сервиса из hwservicemanager:

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

Теперь у клиента есть интерфейс IFoo, и он может вызывать его методы так, как если бы это была реализация локального класса. На самом деле реализация может выполняться в том же процессе, в другом процессе или даже на другом устройстве (с удаленным доступом к HAL). Поскольку клиент вызвал getService для объекта IFoo, включенного в версию 1.0 пакета, hwservicemanager возвращает реализацию сервера, только если она совместима с клиентами 1.0. На практике это означает, что только реализации на стороне сервера с версией 1.n (версия x.(y+1) интерфейса должна быть расширена (унаследована) от x.y).

Кроме того, для преобразования между разными интерфейсами предоставляется метод castFrom. Этот метод работает, выполняя вызов IPC к удаленному интерфейсу, чтобы убедиться, что базовый тип совпадает с запрошенным. Если запрошенный тип недоступен, возвращается значение nullptr.

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

Асинхронные обратные вызовы

Многие существующие реализации HAL взаимодействуют с асинхронным оборудованием, а это означает, что им нужен асинхронный способ уведомления клиентов о новых произошедших событиях. Интерфейс HIDL можно использовать в качестве асинхронного обратного вызова, поскольку функции интерфейса HIDL могут принимать объекты интерфейса HIDL в качестве параметров.

Пример файла интерфейса IFooCallback.hal:

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

Пример нового метода в IFoo, который принимает параметр 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);
};

Клиент, использующий интерфейс IFoo, является сервером интерфейса IFooCallback. Он предоставляет реализацию 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
    }
};

Также можно просто передать его существующему экземпляру интерфейса IFoo:

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

Сервер, реализующий IFoo, получает этот объект как sp<IFooCallback>. Он может сохранить обратный вызов и использовать его для связи с клиентом, когда потребуется.

Получатели в случае смерти

Поскольку реализация сервиса может выполняться в другом процессе, может случиться так, что процесс, реализующий интерфейс, завершится, а клиент останется активным. Любые вызовы объекта интерфейса, размещенного в процессе, который был завершен, завершаются неудачно с ошибкой передачи (isOK() возвращает false). Единственный способ устранить такую ошибку – запросить новый экземпляр сервиса, вызвав I<InterfaceName>::getService(). Это работает, только если процесс, который завершился со сбоем, перезапущен и повторно зарегистрировал свои сервисы в servicemanager (что обычно верно для реализаций HAL).

Вместо того чтобы реагировать на это, клиенты интерфейса могут зарегистрировать получателя уведомления о сбое, чтобы получать уведомления о сбое сервиса. Чтобы зарегистрироваться для получения таких уведомлений через полученный интерфейс IFoo, клиент может выполнить следующие действия:

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

Параметр recipient должен быть реализацией интерфейса android::hardware::hidl_death_recipient, предоставляемого HIDL. Он содержит один метод serviceDied(), который вызывается из потока в пуле потоков RPC, когда процесс, в котором размещен интерфейс, завершается:

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
    }
}

Параметр cookie содержит файл cookie, переданный с помощью linkToDeath(), а параметр who – слабый указатель на объект, представляющий сервис в клиенте. В приведенном выше примере звонка cookie равно 1481, а who равно foo.

Также можно отменить регистрацию получателя уведомлений о смерти:

foo->unlinkToDeath(recipient);