Бэкэнды AIDL

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

AIDL имеет следующие серверные части:

Бэкенд Язык Поверхность API Системы сборки
Джава Джава SDK/SystemApi (стабильная*) все
НДК С++ libbinder_ndk (стабильная*) help_interface
CPP С++ либбиндер (нестабильный) все
Ржавчина Ржавчина libbinder_rs (нестабильный) help_interface
  • Эти поверхности API стабильны, но многие API, например для управления службами, зарезервированы для внутреннего использования платформы и недоступны для приложений. Дополнительные сведения об использовании AIDL в приложениях см. в документации для разработчиков .
  • Бэкэнд Rust был представлен в Android 12; серверная часть NDK доступна с Android 10.
  • Крейт Rust построен поверх libbinder_ndk . APEX используют ящик для переплетов так же, как и все остальные на стороне системы. Часть Rust упакована в APEX и поставляется внутри него. Это зависит от libbinder_ndk.so в системном разделе.

Системы сборки

В зависимости от серверной части существует два способа компиляции AIDL в код-заглушку. Дополнительные сведения о системах сборки см. в справочнике по модулям Soong .

Основная система сборки

В любом cc_ или java_ Android.bp (или в их эквивалентах Android.mk ) файлы .aidl могут быть указаны в качестве исходных файлов. В этом случае используются бэкенды Java/CPP AIDL (а не бэкэнд NDK), а классы для использования соответствующих файлов AIDL автоматически добавляются в модуль. Такие параметры, как local_include_dirs , сообщающие системе сборки корневой путь к файлам AIDL в этом модуле, могут быть указаны в этих модулях в группе aidl: Обратите внимание, что серверная часть Rust предназначена только для использования с Rust. Модули rust_ обрабатываются иначе, поскольку файлы AIDL не указываются как исходные файлы. Вместо этого модуль aidl_interface создает rustlib под названием <aidl_interface name>-rust , с которым можно слинковаться. Подробнее см. пример Rust AIDL .

help_interface

См. Стабильный AIDL . Типы, используемые с этой системой сборки, должны быть структурированы; то есть выражено в AIDL напрямую. Это означает, что нельзя использовать пользовательские посылки.

Типы

Компилятор aidl можно рассматривать как эталонную реализацию типов. Когда вы создаете интерфейс, вызовите aidl --lang=<backend> ... , чтобы увидеть результирующий файл интерфейса. Когда вы используете модуль aidl_interface , вы можете просмотреть вывод в out/soong/.intermediates/<path to module>/ .

Тип Java/AIDL Тип С++ Тип НДК Тип ржавчины
логический логический логический логический
байт int8_t int8_t i8
уголь char16_t char16_t U16
инт int32_t int32_t i32
длинный int64_t int64_t i64
плавать плавать плавать ф32
двойной двойной двойной ф64
Нить андроид :: String16 станд::строка Нить
android.os.Parcelable Android:: Парселабле Н/Д Н/Д
IBinder Android::IBinder ндк::SpAIBinder связующее::SpIBinder
Т[] std::vector<T> std::vector<T> В: &Т
Вышел: Век<T>
байт[] std::vector<uint8_t> std::vector<int8_t> 1 В: &[u8]
Ушел: Век<u8>
Список<T> std::vector<T> 2 std::vector<T> 3 В: &[Т] 4
Вышел: Век<T>
дескриптор файла android::base::unique_fd Н/Д binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk:: ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
тип интерфейса (Т) android::sp<T> std::shared_ptr<T> связующее::сильный
раздельный тип (T) Т Т Т
тип соединения (T) 5 Т Т Т
Т[Н] 6 std::array<T, N> std::array<T, N> [Т; Н]

1. В Android 12 или более поздней версии массивы байтов используют uint8_t вместо int8_t по соображениям совместимости.

2. Серверная часть C++ поддерживает List<T> , где T — одно из значений String , IBinder , ParcelFileDescriptor или разделяемое. В Android T (экспериментальная версия AOSP) или выше T может быть любым непримитивным типом (включая типы интерфейса), кроме массивов. AOSP рекомендует использовать такие типы массивов, как T[] , поскольку они работают во всех бэкендах.

3. Серверная часть NDK поддерживает List<T> , где T — это String , ParcelFileDescriptor или разделяемый. В Android T (экспериментальная версия AOSP) или выше T может быть любым непримитивным типом, кроме массивов.

4. Типы передаются по-разному для кода Rust в зависимости от того, являются ли они входными (аргумент) или выходными (возвращаемое значение).

5. Типы Union поддерживаются в Android 12 и выше.

6. В Android T (экспериментальная версия AOSP) и выше поддерживаются массивы фиксированного размера. Массивы фиксированного размера могут иметь несколько измерений (например, int[3][4] ). В бэкенде Java массивы фиксированного размера представлены как типы массивов.

Направленность (вход/выход/вход)

При указании типов аргументов функций вы можете указать их как in , out или inout . Это определяет, в каком направлении передается информация для вызова IPC. in — это направление по умолчанию, и оно указывает, что данные передаются от вызывающего объекта к вызываемому. out означает, что данные передаются от вызываемого к вызывающему. inout представляет собой комбинацию обоих из них. Однако команда разработчиков Android рекомендует избегать использования спецификатора аргумента inout . Если вы используете inout с версионным интерфейсом и более старой вызывающей стороной, дополнительные поля, присутствующие только в вызывающей стороне, сбрасываются до значений по умолчанию. Что касается Rust, inout тип ввода получает &mut Vec<T> , а тип ввода списка получает & inout &mut Vec<T> .

UTF8/UTF16

С серверной частью CPP вы можете выбрать, будут ли строки utf-8 или utf-16. Объявляйте строки как @utf8InCpp String в AIDL, чтобы автоматически преобразовывать их в utf-8. Бэкэнды NDK и Rust всегда используют строки utf-8. Дополнительные сведения об аннотации utf8InCpp см. в разделе Аннотации в AIDL .

Обнуляемость

Вы можете аннотировать типы, которые могут быть нулевыми в бэкенде Java, с помощью @nullable , чтобы предоставлять нулевые значения для бэкендов CPP и NDK. В бэкенде Rust эти типы @nullable представлены как Option<T> . Собственные серверы по умолчанию отклоняют нулевые значения. Единственными исключениями являются типы interface и IBinder , которые всегда могут быть нулевыми для чтения NDK и записи CPP/NDK. Дополнительные сведения об аннотации, nullable , см. в разделе Аннотации в AIDL .

Пользовательские посылки

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

    package my.package;
    parcelable Foo;

или с объявлением заголовка C++:

    package my.package;
    parcelable Foo cpp_header "my/package/Foo.h";

Затем вы можете использовать этот фрагмент в качестве типа в файлах AIDL, но он не будет сгенерирован AIDL.

Rust не поддерживает пользовательские пакеты.

Значения по умолчанию

Структурированные посылки могут объявлять значения по умолчанию для каждого поля для примитивов, String и массивов этих типов.

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

В серверной части Java, когда значения по умолчанию отсутствуют, поля инициализируются нулевыми значениями для примитивных типов и null значением для непримитивных типов.

В других бэкендах поля инициализируются с инициализированными значениями по умолчанию, когда значения по умолчанию не определены. Например, в бэкенде C++ поля String инициализируются как пустая строка, а поля List<T> инициализируются как пустой vector<T> . Поля @nullable инициализируются как поля с нулевым значением.

Обработка ошибок

В ОС Android предусмотрены встроенные типы ошибок, которые службы могут использовать при сообщении об ошибках. Они используются связующим и могут использоваться любыми службами, реализующими интерфейс связующего. Их использование хорошо задокументировано в определении AIDL, и они не требуют определенного пользователем состояния или возвращаемого типа.

Когда функция AIDL сообщает об ошибке, функция не может инициализировать или изменять выходные параметры. В частности, выходные параметры могут быть изменены, если ошибка возникает во время распаковки, а не во время обработки самой транзакции. В общем, при получении ошибки от функции inout все входные и выходные параметры out а также возвращаемое значение (которое действует как out параметр в некоторых бэкендах) следует считать находящимися в неопределенном состоянии.

Если для интерфейса AIDL требуются дополнительные значения ошибок, которые не охватываются встроенными типами ошибок, они могут использовать специальную встроенную ошибку для конкретной службы, которая позволяет включать значение ошибки для конкретной службы, определенное пользователем. . Эти специфичные для службы ошибки обычно определяются в интерфейсе AIDL как const int или enum с поддержкой int и не анализируются компоновщиком.

В Java ошибки сопоставляются с исключениями, такими как android.os.RemoteException . Для особых сервисных исключений Java использует android.os.ServiceSpecificException вместе с определяемой пользователем ошибкой.

Собственный код в Android не использует исключения. Серверная часть CPP использует android::binder::Status . Серверная часть NDK использует ndk::ScopedAStatus . Каждый метод, сгенерированный AIDL, возвращает один из них, представляющий статус метода. Серверная часть Rust использует те же значения кода исключения, что и NDK, но преобразует их в собственные ошибки Rust ( StatusCode , ExceptionCode ) перед доставкой пользователю. Для ошибок, связанных со службой, возвращаемый Status или ScopedAStatus использует EX_SERVICE_SPECIFIC вместе с ошибкой, определенной пользователем.

Встроенные типы ошибок можно найти в следующих файлах:

Бэкенд Определение
Джава android/os/Parcel.java
CPP binder/Status.h
НДК android/binder_status.h
Ржавчина android/binder_status.h

Использование различных бэкендов

Эти инструкции относятся к коду платформы Android. В этих примерах используется определенный тип my.package.IFoo . Инструкции по использованию бэкенда Rust см. в примере Rust AIDL на странице Android Rust Patterns .

Импорт типов

Независимо от того, является ли определенный тип интерфейсом, разделяемым или объединением, вы можете импортировать его в Java:

    import my.package.IFoo;

Или в бэкэнде CPP:

    #include <my/package/IFoo.h>

Или в бэкэнде NDK (обратите внимание на дополнительное пространство имен aidl ):

    #include <aidl/my/package/IFoo.h>

Или в бэкенде Rust:

    use my_package::aidl::my::package::IFoo;

Хотя вы можете импортировать вложенный тип в Java, в бэкендах CPP/NDK вы должны включить заголовок для его корневого типа. Например, при импорте вложенного типа Bar , определенного в my/package/IFoo.aidl ( IFoo — это корневой тип файла), вы должны включить <my/package/IFoo.h> для бэкенда CPP (или <aidl/my/package/IFoo.h> для серверной части NDK).

Внедрение услуг

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

    package my.package;
    interface IFoo {
        int doFoo();
    }

В Java вы должны расширить этот класс:

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        @Override
        int doFoo() { ... }
    }

В бэкенде CPP:

    #include <my/package/BnFoo.h>
    class MyFoo : public my::package::BnFoo {
        android::binder::Status doFoo(int32_t* out) override;
    }

В бэкэнде NDK (обратите внимание на дополнительное пространство имен aidl ):

    #include <aidl/my/package/BnFoo.h>
    class MyFoo : public aidl::my::package::BnFoo {
        ndk::ScopedAStatus doFoo(int32_t* out) override;
    }

В бэкенде Rust:

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    impl IFoo for MyFoo {
        fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

Регистрация и получение услуг

Службы на платформе Android обычно регистрируются в процессе servicemanager . В дополнение к приведенным ниже API некоторые API проверяют службу (это означает, что они возвращаются немедленно, если служба недоступна). Точные сведения см. в соответствующем интерфейсе servicemanager . Эти операции можно выполнять только при компиляции на платформе Android.

В Java:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // getting
    myService = IFoo.Stub.asInterface(ServiceManager.getService("service-name"));
    // waiting until service comes up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));

В бэкенде CPP:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // getting
    status_t err = getService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

В бэкэнде NDK (обратите внимание на дополнительное пространство имен aidl ):

    #include <android/binder_manager.h>
    // registering
    status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // getting
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_waitForService("service-name")));

В бэкенде Rust:

use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_binder(
        my_service,
        BinderFeatures::default(),
    );
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    // Does not return - spawn or perform any work you mean to do before this call.
    binder::ProcessState::join_thread_pool()
}

Вы можете запросить получение уведомления, когда служба, на которой размещается связующее звено, умирает. Это может помочь избежать утечки прокси-серверов обратного вызова или помочь в устранении ошибок. Сделайте эти вызовы для прокси-объектов связывателя.

  • В Java используйте android.os.IBinder::linkToDeath .
  • В бэкенде CPP используйте android::IBinder::linkToDeath .
  • В бэкэнде NDK используйте AIBinder_linkToDeath .
  • В бэкенде Rust создайте объект DeathRecipient , затем вызовите my_binder.link_to_death(&mut my_death_recipient) . Обратите внимание, что, поскольку обратный вызов принадлежит DeathRecipient , вы должны поддерживать этот объект в рабочем состоянии до тех пор, пока хотите получать уведомления.

Отчеты об ошибках и API отладки для сервисов

Когда запускаются отчеты об ошибках (например, с помощью adb bugreport ), они собирают информацию со всей системы, чтобы помочь в отладке различных проблем. Для служб AIDL отчеты об ошибках используют двоичные dumpsys для всех служб, зарегистрированных в диспетчере служб, для выгрузки их информации в отчет об ошибках. Вы также можете использовать dumpsys в командной строке для получения информации от службы с помощью dumpsys SERVICE [ARGS] . В бэкэндах C++ и Java вы можете управлять порядком, в котором выгружаются службы, используя дополнительные аргументы для addService . Вы также можете использовать dumpsys --pid SERVICE , чтобы получить PID службы во время отладки.

Чтобы добавить в службу настраиваемый вывод, вы можете переопределить метод dump в своем объекте сервера, как вы реализуете любой другой метод IPC, определенный в файле AIDL. При этом вы должны ограничить дамп разрешением приложения android.permission.DUMP или ограничить дамп определенными UID.

В бэкенде Java:

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

В бэкенде CPP:

    status_t dump(int, const android::android::Vector<android::String16>&) override;

В бэкэнде NDK:

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

В бэкенде Rust при реализации интерфейса укажите следующее (вместо разрешения по умолчанию):

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

Динамическое получение дескриптора интерфейса

Дескриптор интерфейса идентифицирует тип интерфейса. Это полезно при отладке или при наличии неизвестного связующего.

В Java вы можете получить дескриптор интерфейса с помощью такого кода:

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

В бэкенде CPP:

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

Бэкэнды NDK и Rust не поддерживают эту функциональность.

Статическое получение дескриптора интерфейса

Иногда (например, при регистрации сервисов @VintfStability ) вам нужно знать, что такое дескриптор интерфейса статически. В Java вы можете получить дескриптор, добавив такой код:

    import my.package.IFoo;
    ... IFoo.DESCRIPTOR

В бэкенде CPP:

    #include <my/package/BnFoo.h>
    ... my::package::BnFoo::descriptor

В бэкэнде NDK (обратите внимание на дополнительное пространство имен aidl ):

    #include <aidl/my/package/BnFoo.h>
    ... aidl::my::package::BnFoo::descriptor

В бэкенде Rust:

    aidl::my::package::BnFoo::get_descriptor()

Диапазон перечисления

В нативных бэкендах вы можете перебирать возможные значения, которые может принимать перечисление. Из-за соображений размера кода в настоящее время это не поддерживается в Java.

Для перечисления MyEnum , определенного в AIDL, итерация обеспечивается следующим образом.

В бэкенде CPP:

    ::android::enum_range<MyEnum>()

В бэкэнде NDK:

   ::ndk::enum_range<MyEnum>()

В бэкенде Rust:

    MyEnum::enum_range()

Управление потоками

Каждый экземпляр libbinder в процессе поддерживает один пул потоков. В большинстве случаев это должен быть ровно один пул потоков, общий для всех серверных частей. Единственным исключением является случай, когда код поставщика может загрузить другую копию libbinder для взаимодействия с /dev/vndbinder . Поскольку это находится на отдельном узле связывателя, пул потоков не используется совместно.

Для серверной части Java размер пула потоков может только увеличиваться (поскольку он уже запущен):

    BinderInternal.setMaxThreads(<new larger value>);

Для бэкенда CPP доступны следующие операции:

    // set max threadpool count (default is 15)
    status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
    // create threadpool
    ProcessState::self()->startThreadPool();
    // add current thread to threadpool (adds thread to max thread count)
    IPCThreadState::self()->joinThreadPool();

Точно так же в бэкэнде NDK:

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
    ABinderProcess_startThreadPool();
    ABinderProcess_joinThreadPool();

В бэкенде Rust:

    binder::ProcessState::start_thread_pool();
    binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
    binder::ProcessState::join_thread_pool();