Сервисы и передача данных

На этой странице рассказывается, как регистрировать и находить сервисы, а также отправлять данные в сервис, вызывая методы, определенные в интерфейсах в файлах .hal.

Зарегистрировать службы

Серверы интерфейса HIDL (объекты, реализующие интерфейс) можно зарегистрировать как именованные сервисы. Зарегистрированное имя не обязательно должно быть связано с названием интерфейса или пакета. Если название не указано, используется название "default". Его следует использовать для HAL, которым не нужно регистрировать две реализации одного и того же интерфейса. Например, вызов регистрации сервиса на языке C++, определенный в каждом интерфейсе, выглядит так:

status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service");  // if needed

Версия интерфейса HIDL включена в сам интерфейс. Он автоматически связывается с регистрацией сервиса и может быть получен с помощью вызова метода (android::hardware::IInterface::getInterfaceVersion()) в каждом интерфейсе HIDL. Объекты сервера не нужно регистрировать, и их можно передавать через параметры метода HIDL другому процессу, который выполняет вызовы метода HIDL на сервере.

Обзор сервисов

Запросы по коду клиента выполняются для определенного интерфейса по имени и версии, вызывая getService для нужного класса HAL:

// 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 */);

Каждая версия интерфейса HIDL рассматривается как отдельный интерфейс. Таким образом, IFooService версии 1.1 и IFooService версии 2.2 могут быть зарегистрированы как foo_service, а getService("foo_service") в любом интерфейсе получает зарегистрированный сервис для этого интерфейса. Поэтому в большинстве случаев при регистрации или обнаружении не нужно указывать параметр name (то есть используется значение по умолчанию).

Объект интерфейса поставщика также играет роль в методе передачи возвращаемого интерфейса. Для интерфейса IFoo в пакете android.hardware.foo@1.0 интерфейс, возвращаемый IFoo::getService, всегда использует метод передачи, объявленный для android.hardware.foo в манифесте устройства, если запись существует. Если метод передачи недоступен, возвращается nullptr.

В некоторых случаях может потребоваться продолжить работу, даже не получив сервис. Это может произойти, например, когда клиент хочет самостоятельно управлять уведомлениями службы или в диагностической программе (такой как atrace), которой необходимо получить все hwservices и извлечь их. В этом случае предоставляются дополнительные API, например tryGetService в C++ или getService("instance-name", false) в Java. Устаревший API getService, предоставляемый в Java, также должен использоваться с уведомлениями сервиса. Использование этого API не позволяет избежать состояния гонки, когда сервер регистрируется после того, как клиент запрашивает его с помощью одного из этих API без повторных попыток.

Уведомления о прекращении работы сервиса

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

  1. Создайте подкласс для класса/интерфейса HIDL hidl_death_recipient (в коде C++, а не в HIDL).
  2. Переопределите его метод serviceDied().
  3. Создайте экземпляр объекта подкласса hidl_death_recipient.
  4. Вызовите метод linkToDeath() для отслеживаемого сервиса, передав объект интерфейса IDeathRecipient. Обратите внимание, что этот метод не передает право собственности на получателя смерти или прокси, в котором он вызывается.

Пример псевдокода (C++ и Java похожи):

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);

Один и тот же получатель может быть зарегистрирован в нескольких сервисах.

Передача данных

Данные можно отправлять в сервис, вызывая методы, определенные в интерфейсах в файлах .hal. Существует два типа методов:

  • Блокирующие методы ждут, пока сервер не выдаст результат.
  • Методы Oneway отправляют данные только в одном направлении и не блокируют. Если объем данных, передаваемых в вызовах RPC, превышает ограничения реализации, вызовы могут быть заблокированы или вернуть сообщение об ошибке (поведение ещё не определено).

Метод, который не возвращает значение, но не объявлен как oneway, по-прежнему блокирует выполнение.

Все методы, объявленные в интерфейсе HIDL, вызываются в одном направлении: либо из HAL, либо в HAL. В интерфейсе не указано, в каком направлении вызывается функция. В архитектурах, где звонки должны инициироваться из HAL, в пакете HAL должно быть два или более интерфейсов, и каждый процесс должен использовать подходящий интерфейс. Слова клиент и сервер используются в отношении направления вызова интерфейса (т.е. HAL может быть сервером одного интерфейса и клиентом другого интерфейса).

Обратные вызовы

Термин callback относится к двум разным понятиям: синхронному и асинхронному.

Синхронные обратные вызовы используются в некоторых методах HIDL, которые возвращают данные. Метод HIDL, который возвращает более одного значения (или одно значение непримитивного типа), возвращает результаты с помощью функции обратного вызова. Если возвращается только одно значение и оно относится к примитивному типу, обратный вызов не используется, а значение возвращается из метода. Сервер реализует методы HIDL, а клиент реализует обратные вызовы.

Асинхронные обратные вызовы позволяют серверу интерфейса HIDL инициировать вызовы. Это делается путем передачи экземпляра второго интерфейса через первый. Клиент первого интерфейса должен выступать в качестве сервера второго. Сервер первого интерфейса может вызывать методы объекта второго интерфейса. Например, реализация HAL может асинхронно отправлять информацию обратно в процесс, который ее использует, вызывая методы объекта интерфейса, созданного и обслуживаемого этим процессом. Методы в интерфейсах, используемых для асинхронного обратного вызова, могут быть блокирующими (и возвращать значения вызывающей стороне) или oneway. Пример можно найти в разделе "Асинхронные обратные вызовы" статьи HIDL C++.

Чтобы упростить управление памятью, вызовы методов и обратные вызовы принимают только параметры in и не поддерживают параметры out или inout.

Лимиты на транзакцию

Ограничения на объем данных, отправляемых в методах и обратных вызовах HIDL, не применяются. Однако вызовы, превышающие 4 КБ на транзакцию, считаются чрезмерными. В этом случае рекомендуется переработать интерфейс HIDL. Ещё одно ограничение – ресурсы, доступные инфраструктуре HIDL для обработки нескольких одновременных транзакций. Несколько транзакций могут выполняться одновременно из-за того, что несколько потоков или процессов отправляют вызовы в один или несколько процессов oneway, которые не обрабатываются быстро принимающим процессом. Максимальный общий объем пространства, доступный для всех одновременных транзакций, по умолчанию составляет 1 МБ.

В хорошо спроектированном интерфейсе превышение этих ограничений ресурсов не должно происходить. Если это произойдет, вызов, который их превысил, может либо заблокироваться до тех пор, пока ресурсы не станут доступными, либо сигнализировать об ошибке транспорта. Каждый случай превышения лимита на транзакцию или переполнения ресурсов HIDL из-за агрегированных незавершенных транзакций регистрируется для облегчения отладки.

Реализация методов

HIDL генерирует заголовочные файлы, объявляющие необходимые типы, методы и обратные вызовы на целевом языке (C++ или Java). Прототип методов и обратных вызовов, определенных в HIDL, одинаков как для клиентского, так и для серверного кода. Система HIDL предоставляет реализации методов прокси на стороне вызывающего объекта, которые организуют данные для передачи IPC, и код заглушки на стороне вызываемого объекта, который передает данные в реализации методов, созданные разработчиком.

Вызывающая функция (метод HIDL или обратный вызов) владеет структурами данных, переданными в функцию, и сохраняет право собственности после вызова. Во всех случаях вызываемой функции не нужно освобождать или высвобождать хранилище.

  • В C++ данные могут быть доступны только для чтения (попытки записи в них могут привести к ошибке сегментации) и действительны в течение вызова. Клиент может скопировать данные, чтобы использовать их после завершения вызова.
  • В Java код получает локальную копию данных (обычный объект Java), которую он может сохранить и изменить или разрешить сборку мусора.

Передача данных без RPC

В HIDL есть два способа передачи данных без использования вызова RPC: общая память и очередь быстрых сообщений (FMQ). Оба поддерживаются только в C++.

  • Общее воспоминание. Встроенный тип HIDL memory используется для передачи объекта, представляющего выделенную общую память. Может использоваться в процессе получения для сопоставления общей памяти.
  • Fast Message Queue (FMQ). HIDL предоставляет тип очереди сообщений на основе шаблонов, который реализует передачу сообщений без ожидания. В режиме сквозной передачи или binderized (связанном с binder) не используются ядро или планировщик (межплатформенная связь не имеет этих свойств). Обычно HAL настраивает свою часть очереди, создавая объект, который можно передать через RPC с помощью параметра встроенного типа HIDL MQDescriptorSync или MQDescriptorUnsync. Этот объект может использоваться принимающим процессом для настройки другого конца очереди.
    • Очереди синхронизации не могут переполняться и могут иметь только один считыватель.
    • Очереди Unsync могут переполняться и иметь много читателей, каждый из которых должен вовремя прочитать данные, иначе они будут потеряны.
    Ни один из типов не может быть меньше нуля (чтение из пустой очереди не допускается), и для каждого типа может быть только один записывающий процесс.

Подробнее о FMQ можно узнать в разделе Fast Message Queue (FMQ).