В Android 11 появилась возможность использовать AIDL для HAL в Android. Это позволяет реализовать части Android без HIDL. Переходите HAL на использование исключительно AIDL, где это возможно (когда восходящие HAL используют HIDL, необходимо использовать HIDL).
HAL, использующие AIDL для связи между компонентами платформы, например, в system.img
, и аппаратными компонентами, например, vendor.img
, должны использовать Stable AIDL. Однако для связи внутри раздела, например, от одного HAL к другому, нет никаких ограничений на использование механизма IPC.
Мотивация
AIDL существует дольше, чем HIDL, и используется во многих других местах, например, между компонентами платформы Android или в приложениях. Теперь, когда AIDL имеет поддержку стабильности, можно реализовать весь стек с помощью одной среды выполнения IPC. AIDL также имеет лучшую систему управления версиями, чем HIDL.
- Использование одного языка IPC означает, что нужно изучать, отлаживать, оптимизировать и защищать только одну вещь.
- AIDL поддерживает управление версиями на месте для владельцев интерфейса:
- Владельцы могут добавлять методы в конец интерфейсов или поля в объекты. Это означает, что с годами легче обновлять код, а также годовые затраты меньше (типы можно изменять на месте, и нет необходимости в дополнительных библиотеках для каждой версии интерфейса).
- Интерфейсы расширений можно подключать во время выполнения, а не в системе типов, поэтому нет необходимости перебазировать последующие расширения на более новые версии интерфейсов.
- Существующий интерфейс AIDL может использоваться напрямую, когда его владелец решит его стабилизировать. Раньше в HIDL приходилось создавать полную копию интерфейса.
Сборка на основе среды выполнения AIDL
AIDL имеет три разных бэкэнда: Java, NDK, CPP. Чтобы использовать Stable AIDL, вы всегда должны использовать системную копию libbinder по адресу system/lib*/libbinder.so
и разговаривать по /dev/binder
. Для кода в вендорном образе это означает, что libbinder
(из VNDK) использовать нельзя: у этой библиотеки нестабильный C++ API и нестабильные внутренние компоненты. Вместо этого собственный код поставщика должен использовать серверную часть NDK AIDL, ссылаться на libbinder_ndk
(который поддерживается системой libbinder.so
) и связываться с библиотеками NDK, созданными записями aidl_interface
. Точные имена модулей см. в правилах именования модулей .
Напишите интерфейс AIDL HAL.
Чтобы интерфейс AIDL мог использоваться между системой и поставщиком, в интерфейс необходимо внести два изменения:
- Каждое определение типа должно быть аннотировано
@VintfStability
. - Объявление
aidl_interface
должно включатьstability: "vintf",
.
Эти изменения может вносить только владелец интерфейса.
Когда вы вносите эти изменения, для работы интерфейс должен присутствовать в манифесте VINTF . Проверьте это (и связанные с ним требования, например проверку того, что выпущенные интерфейсы заморожены) с помощью теста VTS vts_treble_vintf_vendor_test
. Вы можете использовать интерфейс @VintfStability
без этих требований, вызвав AIBinder_forceDowngradeToLocalStability
в серверной части NDK, android::Stability::forceDowngradeToLocalStability
в внутренней части C++ или android.os.Binder#forceDowngradeToSystemStability
в внутренней части Java для объекта связывания перед его отправкой. в другой процесс. Понижение уровня службы до стабильности поставщика не поддерживается в Java, поскольку все приложения выполняются в системном контексте.
Кроме того, для максимальной переносимости кода и во избежание потенциальных проблем, таких как ненужные дополнительные библиотеки, отключите серверную часть CPP.
Обратите внимание, что использование backends
в приведенном ниже примере кода является правильным, поскольку их три (Java, NDK и CPP). В приведенном ниже коде показано, как конкретно выбрать серверную часть CPP, чтобы отключить ее.
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
Найдите интерфейсы AIDL HAL
Стабильные интерфейсы AIDL AOSP для HAL находятся в тех же базовых каталогах, что и интерфейсы HIDL, в папках aidl
.
- аппаратное обеспечение/интерфейсы: для интерфейсов, обычно предоставляемых аппаратным обеспечением.
- рамки/оборудование/интерфейсы: для высокоуровневых интерфейсов, предоставляемых оборудованию.
- система/оборудование/интерфейсы: для низкоуровневых интерфейсов, предоставляемых оборудованию.
Вам следует поместить интерфейсы расширения в другие подкаталоги hardware/interfaces
в vendor
или hardware
.
Интерфейсы расширения
В каждом выпуске Android есть набор официальных интерфейсов AOSP. Когда партнеры Android хотят добавить функциональность в эти интерфейсы, им не следует изменять их напрямую, поскольку это будет означать, что их среда выполнения Android несовместима со средой выполнения AOSP Android. Для устройств GMS отказ от изменения этих интерфейсов также является гарантией продолжения работы образа GSI.
Расширения могут регистрироваться двумя разными способами:
- во время выполнения см. прикрепленные расширения .
- автономный, зарегистрированный по всему миру и в VINTF.
Однако расширение зарегистрировано, когда компоненты, специфичные для поставщика (то есть не являющиеся частью восходящего AOSP), используют интерфейс, вероятность конфликта слияния отсутствует. Однако при внесении изменений в вышестоящие компоненты AOSP могут возникнуть конфликты слияния, поэтому рекомендуются следующие стратегии:
- дополнения к интерфейсу могут быть переданы в AOSP в следующем выпуске.
- Дополнения интерфейса, которые обеспечивают дополнительную гибкость без конфликтов слияния, могут быть добавлены в следующую версию.
Расширение посылки: ParcelableHolder
ParcelableHolder
— это Parcelable
, который может содержать другой Parcelable
. Основной вариант использования ParcelableHolder
— сделать Parcelable
расширяемым. Например, представьте, что разработчики устройств ожидают возможности расширить Parcelable
, определенный AOSP, AospDefinedParcelable
, включив в него дополнительные функции.
Раньше без ParcelableHolder
разработчики устройств не могли модифицировать стабильный интерфейс AIDL, определенный AOSP, поскольку добавление дополнительных полей было бы ошибкой:
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
Как видно из предыдущего кода, эта практика нарушена, поскольку поля, добавленные разработчиком устройства, могут иметь конфликт при обновлении Parcelable в следующих выпусках Android.
Используя ParcelableHolder
, владелец Parcelable может определить точку расширения в Parcelable
.
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Затем разработчики устройств могут определить свой собственный Parcelable
для своего расширения.
parcelable OemDefinedParcelable {
String x;
int[] y;
}
Наконец, новый Parcelable
можно прикрепить к исходному Parcelable
с помощью поля ParcelableHolder
.
// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;
ap.extension.setParcelable(op);
...
OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);
// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();
ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);
...
std::shared_ptr<OemDefinedParcelable> op_ptr;
ap.extension.getParcelable(&op_ptr);
// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);
...
std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);
// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });
ap.extension.set_parcelable(Rc::clone(&op));
...
let op = ap.extension.get_parcelable::<OemDefinedParcelable>();
Имена экземпляров сервера AIDL HAL
По соглашению службы AIDL HAL имеют имя экземпляра в формате $package.$type/$instance
. Например, экземпляр вибратора HAL зарегистрирован как android.hardware.vibrator.IVibrator/default
.
Напишите сервер AIDL HAL.
@VintfStability
Серверы AIDL должны быть объявлены в манифесте VINTF, например так:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
В противном случае им следует зарегистрировать службу AIDL обычным способом. При запуске тестов VTS ожидается, что все объявленные AIDL HAL доступны.
Напишите клиент AIDL
Клиенты AIDL должны заявить себя в матрице совместимости, например так:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
Преобразование существующего HAL из HIDL в AIDL
Используйте инструмент hidl2aidl
для преобразования интерфейса HIDL в AIDL.
Возможности hidl2aidl
:
- Создайте файлы
.aidl
на основе файлов.hal
для данного пакета. - Создайте правила сборки для вновь созданного пакета AIDL со всеми включенными серверными модулями.
- Создайте методы перевода в серверных модулях Java, CPP и NDK для перевода из типов HIDL в типы AIDL.
- Создайте правила сборки для библиотек перевода с необходимыми зависимостями.
- Создайте статические утверждения, чтобы гарантировать, что перечислители HIDL и AIDL имеют одинаковые значения в серверных модулях CPP и NDK.
Выполните следующие действия, чтобы преобразовать пакет файлов .hal в файлы .aidl:
Создайте инструмент, расположенный в
system/tools/hidl/hidl2aidl
.Создание этого инструмента из новейшего источника обеспечивает наиболее полный опыт. Вы можете использовать последнюю версию для преобразования интерфейсов в старых ветках из предыдущих выпусков.
m hidl2aidl
Запустите инструмент, указав выходной каталог, за которым следует конвертируемый пакет.
При необходимости используйте аргумент
-l
, чтобы добавить содержимое нового файла лицензии вверху всех созданных файлов. Обязательно используйте правильную лицензию и дату.hidl2aidl -o <output directory> -l <file with license> <package>
Например:
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
Прочитайте сгенерированные файлы и устраните любые проблемы с преобразованием.
-
conversion.log
содержит все необработанные проблемы, которые необходимо устранить в первую очередь. - Созданные файлы
.aidl
могут содержать предупреждения и предложения, которые могут потребовать действий. Эти комментарии начинаются с//
. - Воспользуйтесь возможностью почистить и улучшить пакет.
- Проверьте аннотацию
@JavaDerive
на наличие функций, которые могут потребоваться, напримерtoString
илиequals
.
-
Стройте только те цели, которые вам нужны.
- Отключите серверные части, которые не будут использоваться. Предпочитайте бэкэнд NDK бэкэнду CPP, см. Выбор среды выполнения .
- Удалите библиотеки перевода или любой их сгенерированный код, который не будет использоваться.
См . Основные различия AIDL/HIDL .
- Использование встроенного
Status
и исключений AIDL обычно улучшает интерфейс и устраняет необходимость в другом типе статуса, специфичном для интерфейса. - Аргументы интерфейса AIDL в методах по умолчанию не
@nullable
, как в HIDL.
- Использование встроенного
SEPolicy для AIDL HAL
Тип службы AIDL, видимый коду поставщика, должен иметь атрибут hal_service_type
. В остальном конфигурация sepolicy такая же, как и у любой другой службы AIDL (хотя для HAL существуют специальные атрибуты). Вот пример определения контекста службы HAL:
type hal_foo_service, service_manager_type, hal_service_type;
Для большинства сервисов, определенных платформой, контекст сервиса с правильным типом уже добавлен (например, android.hardware.foo.IFoo/default
уже будет помечен как hal_foo_service
). Однако если клиент платформы поддерживает несколько имен экземпляров, дополнительные имена экземпляров необходимо добавить в файлы service_contexts
для конкретного устройства.
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
Атрибуты HAL необходимо добавлять при создании нового типа HAL. Определенный атрибут HAL может быть связан с несколькими типами служб (каждый из которых может иметь несколько экземпляров, как мы только что обсуждали). Для HAL foo
у нас есть hal_attribute(foo)
. Этот макрос определяет атрибуты hal_foo_client
и hal_foo_server
. Для данного домена макросы hal_client_domain
и hal_server_domain
связывают домен с данным атрибутом HAL. Например, системный сервер, являющийся клиентом этого HAL, соответствует политике hal_client_domain(system_server, hal_foo)
. Сервер HAL аналогичным образом включает hal_server_domain(my_hal_domain, hal_foo)
. Обычно для данного атрибута HAL мы также создаем домен, например hal_foo_default
для справки или примеров HAL. Однако некоторые устройства используют эти домены для своих собственных серверов. Различие между доменами для нескольких серверов имеет значение только в том случае, если у нас есть несколько серверов, которые обслуживают один и тот же интерфейс и нуждаются в разных наборах разрешений в своих реализациях. Во всех этих макросах hal_foo
на самом деле не является объектом sepolicy. Вместо этого этот токен используется этими макросами для ссылки на группу атрибутов, связанных с парой клиент-сервер.
Однако до сих пор мы не связали hal_foo_service
и hal_foo
(пару атрибутов из hal_attribute(foo)
). Атрибут HAL связан со службами AIDL HAL с помощью макроса hal_attribute_service
(HAL HIDL использует макрос hal_attribute_hwservice
). Например, hal_attribute_service(hal_foo, hal_foo_service)
. Это означает, что процессы hal_foo_client
могут получить доступ к HAL, а процессы hal_foo_server
могут зарегистрировать HAL. Применение этих правил регистрации осуществляется менеджером контекста ( servicemanager
). Обратите внимание: имена служб не всегда могут соответствовать атрибутам HAL. Например, мы можем увидеть hal_attribute_service(hal_foo, hal_foo2_service)
. Однако в целом, поскольку это подразумевает, что службы всегда используются вместе, мы могли бы рассмотреть возможность удаления hal_foo2_service
и использования hal_foo_service
для всех наших контекстов служб. В большинстве HAL, которые устанавливают несколько hal_attribute_service
, исходное имя атрибута HAL недостаточно общее и не может быть изменено.
Объединив все это, пример HAL выглядит следующим образом:
public/attributes:
// define hal_foo, hal_foo_client, hal_foo_server
hal_attribute(foo)
public/service.te
// define hal_foo_service
type hal_foo_service, hal_service_type, protected_service, service_manager_type
public/hal_foo.te:
// allow binder connection from client to server
binder_call(hal_foo_client, hal_foo_server)
// allow client to find the service, allow server to register the service
hal_attribute_service(hal_foo, hal_foo_service)
// allow binder communication from server to service_manager
binder_use(hal_foo_server)
private/service_contexts:
// bind an AIDL service name to the selinux type
android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0
private/<some_domain>.te:
// let this domain use the hal service
binder_use(some_domain)
hal_client_domain(some_domain, hal_foo)
vendor/<some_hal_server_domain>.te
// let this domain serve the hal service
hal_server_domain(some_hal_server_domain, hal_foo)
Присоединенные интерфейсы расширения
Расширение можно прикрепить к любому интерфейсу связывания, будь то интерфейс верхнего уровня, зарегистрированный непосредственно в диспетчере служб, или подинтерфейс. При получении расширения вы должны подтвердить, что тип расширения соответствует ожидаемому. Расширения можно устанавливать только из процесса, обслуживающего связующее.
Присоединенные расширения следует использовать всякий раз, когда расширение изменяет функциональность существующего HAL. Если необходимы совершенно новые функциональные возможности, этот механизм не требуется, а интерфейс расширения можно зарегистрировать непосредственно у менеджера служб. Присоединенные интерфейсы расширения имеют наибольший смысл, когда они присоединены к субинтерфейсам, поскольку эти иерархии могут быть глубокими или состоять из нескольких экземпляров. Использование глобального расширения для отражения иерархии интерфейса связывания другой службы потребует обширного учета, чтобы обеспечить эквивалентную функциональность напрямую подключенным расширениям.
Чтобы установить расширение для подшивки, используйте следующие API:
- В бэкэнде NDK:
AIBinder_setExtension
. - В серверной части Java:
android.os.Binder.setExtension
- В бэкэнде CPP:
android::Binder::setExtension
- В бэкэнде Rust:
binder::Binder::set_extension
Чтобы получить расширение для подшивки, используйте следующие API:
- В бэкэнде NDK:
AIBinder_getExtension
. - В серверной части Java:
android.os.IBinder.getExtension
. - В серверной части CPP:
android::IBinder::getExtension
- В бэкэнде Rust:
binder::Binder::get_extension
Дополнительную информацию об этих API можно найти в документации функции getExtension
в соответствующем бэкэнде. Пример использования расширений можно найти в hardware/interfaces/tests/extension/vibrator .
Основные различия между AIDL и HIDL
При использовании AIDL HAL или интерфейсов AIDL HAL помните о различиях по сравнению с написанием HIDL HAL.
- Синтаксис языка AIDL ближе к Java. Синтаксис HIDL аналогичен синтаксису C++.
- Все интерфейсы AIDL имеют встроенные статусы ошибок. Вместо создания пользовательских типов состояния создайте целые числа постоянного состояния в файлах интерфейса и используйте
EX_SERVICE_SPECIFIC
в серверных модулях CPP/NDK иServiceSpecificException
в серверной части Java. См. Обработка ошибок . - AIDL не запускает автоматически пулы потоков при отправке объектов связывания. Их необходимо запускать вручную (см. управление потоками ).
- AIDL не прерывается при непроверенных ошибках транспорта (HIDL
Return
прерывается при непроверенных ошибках). - AIDL может объявлять только один тип для каждого файла.
- Аргументы AIDL могут быть указаны как in/out/inout в дополнение к выходному параметру (нет «синхронных обратных вызовов»).
- AIDL использует fd в качестве примитивного типа вместо дескриптора.
- HIDL использует основные версии для несовместимых изменений и второстепенные версии для совместимых изменений. В AIDL обратно совместимые изменения выполняются на месте. AIDL не имеет четкого понятия основных версий; вместо этого это включается в имена пакетов. Например, AIDL может использовать имя пакета
bluetooth2
. - AIDL по умолчанию не наследует приоритет реального времени. Функцию
setInheritRt
необходимо использовать для каждой привязки, чтобы включить наследование приоритета в реальном времени.