Бэкенды AIDL

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

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

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

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

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

Система сборки ядра

В любом 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 .

helpl_interface

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

Типы

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

Тип Java / AIDL Тип C ++ Тип NDK Тип ржавчины
логический bool bool bool
байт int8_t int8_t i8
символ char16_t char16_t u16
int int32_t int32_t i32
длинный int64_t int64_t i64
плавать плавать плавать f32
двойной двойной двойной f64
Нить android :: String16 std :: string Нить
android.os.Parcelable android :: Parcelable N / A N / A
IBinder android :: IBinder ndk :: SpAIBinder связующее :: SpIBinder
Т [] std :: vector <T> std :: vector <T> В: & T
Вышел: Vec <T>
байт[] std :: vector <uint8_t> станд :: вектор <int8_t> 1 В: & [u8]
Вышел: Vec <u8>
Список <T> станд :: вектор <Т> 2 станд :: вектор <T> 3 В: & [Т] 4
Вышел: Vec <T>
FileDescriptor android :: base :: unique_fd N / A binder :: parcel :: ParcelFileDescriptor
ParcelFileDescriptor android :: os :: ParcelFileDescriptor ndk :: ScopedFileDescriptor binder :: parcel :: ParcelFileDescriptor
тип интерфейса (T) android :: sp <T> std :: shared_ptr <T> связующее :: Сильный
посылочный тип (T) Т Т Т
типа объединения (Т) 5 Т Т Т

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

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

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

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

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

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

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

UTF8 / UTF16

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

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

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

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

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

    package my.package;
    parcelable Foo;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Эти инструкции относятся к коду платформы Android. Эти примеры используют определенный тип, my.package.IFoo . Для получения инструкций о том , как использовать бэкенд ржавчины см пример 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> для внутреннего интерфейса НДК).

Реализация услуг

Чтобы реализовать службу, вы должны унаследовать ее от собственного класса-заглушки. Этот класс считывает команды из драйвера связывателя и выполняет методы, которые вы реализуете. Представьте, что у вас есть такой файл 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 .
  • В СРР бэкэнде, использование android::IBinder::linkToDeath .
  • В интерфейсе НДК, используйте AIBinder_linkToDeath .
  • В бэкэнде ржавчины, создать 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 или ограничить сброс для определенных пользователей.

В бэкэнде Java:

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

В бэкэнде C ++:

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

Enum Range

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

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

В бэкэнде CPP:

    ::android::enum_range<MyEnum>()

В бэкэнде NDK:

   ::ndk::enum_range<MyEnum>()

В бэкэнде Rust:

    MyEnum::enum_range()

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

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

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

    BinderInternal.setMaxThreads(<new larger value>);

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

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