Серверные части AIDL

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

В приведенной ниже таблице стабильность API-интерфейса относится к возможности компиляции кода с использованием этого API-интерфейса таким образом, чтобы код мог распространяться независимо от бинарного файла system.img libbinder.so .

В AIDL используются следующие бэкэнды:

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

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

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

Базовая система сборки

В любом Android.bp module cc_ или java_ (или в их эквивалентах Android.mk ) можно указать файлы AIDL ( .aidl ) в качестве исходных файлов. В этом случае используются бэкенды AIDL на Java или C++ (а не на NDK), и классы, использующие соответствующие файлы AIDL, автоматически добавляются в модуль. В этих модулях в группе aidl: можно указать такие параметры, как local_include_dirs (который указывает системе сборки корневой путь к файлам AIDL в этом модуле).

Бэкенд Rust предназначен только для использования с Rust. Модули rust_ обрабатываются иначе: файлы AIDL не указываются в качестве исходных файлов. Вместо этого модуль aidl_interface создает библиотеку rustlib с именем aidl_interface_name -rust , с которой можно выполнить компоновку. Подробности см. в примере Rust AIDL .

aidl_interface

Типы, используемые в системе сборки aidl_interface должны быть структурированными. Для того чтобы быть структурированными, парселблейбы должны содержать поля напрямую, а не быть объявлениями типов, определенных непосредственно в целевых языках. О том, как структурированный AIDL соотносится со стабильным AIDL, см. раздел «Структурированный AIDL против стабильного AIDL» .

Типы

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

Java или тип AIDL тип C++ Тип НДК Тип ржавчины
boolean bool bool bool
byte 8 int8_t int8_t i8
char char16_t char16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
float float float f32
double double double f64
String android::String16 std::string В: &str
Выход: String
android.os.Parcelable android::Parcelable Н/Д Н/Д
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder 9
T[] std::vector<T> std::vector<T> В: &[T]
Выход: Vec<T>
byte[] std::vector std::vector 1 В: &[u8]
Выход: Vec<u8>
List<T> std::vector<T> 2 std::vector<T> 3 В: In: &[T] 4
Выход: Vec<T>
FileDescriptor android::base::unique_fd Н/Д Н/Д
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor 9
Тип интерфейса ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong<dyn T> 9
Тип посылки ( T ) T T T 9
Тип соединения ( T ) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

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

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

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

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

5. В Android 12 и более поздних версиях поддерживаются типы объединения.

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

7. Для создания объекта SharedRefBase для связывателя используйте SharedRefBase::make\<My\>(... args ...) . Эта функция создает объект std::shared_ptr\<T\> , который также управляется внутри системы, если связыватель принадлежит другому процессу. Создание объекта другими способами приводит к двойному владению.

8. См. также Java или AIDL тип byte[] .

9. В Rust типы, не реализующие Default ( IBinder , ParcelFileDescriptor , интерфейсные типы и неструктурированные парсируемые объекты), отображаются в Option<T> когда требуется значение по умолчанию (например, в парсируемых полях, out параметрах и массивах фиксированного размера в этих контекстах), даже без аннотации @nullable . Недопустимость значения null по-прежнему обеспечивается во время выполнения при парсинге, возвращая StatusCode::UNEXPECTED_NULL если значение равно None . Для возвращаемых значений методов, in параметрах (передаваемых как &T ) и inout параметрах (передаваемых как &mut T ), эти типы не оборачиваются в Option<T> если не аннотированы @nullable .

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

При указании типов аргументов функций можно использовать значения in , out или inout . Это определяет направление передачи информации при вызове межпроцессного взаимодействия (IPC).

  • Спецификатор in указывает, что данные передаются от вызывающей стороны к вызываемой. Спецификатор in определяет направление передачи по умолчанию, но если типы данных также могут передаваться out , то необходимо указать направление.

  • Спецификатор out аргумента означает, что данные передаются от вызываемой функции к вызывающей.

  • Спецификатор аргумента inout представляет собой комбинацию обоих этих вариантов. Однако мы рекомендуем избегать использования спецификатора аргумента inout . Если вы используете inout с версионированным интерфейсом и более старой вызываемой функцией, дополнительные поля, присутствующие только в вызывающей функции, сбрасываются до значений по умолчанию. Что касается Rust, обычный тип inout получает &mut T , а тип inout типа списка получает &mut Vec<T> .

interface IRepeatExamples {
    MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
    MyParcelable RepeatParcelableWithIn(in MyParcelable token);
    void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
    void RepeatParcelableWithInOut(inout MyParcelable param);
}

UTF-8 и UTF-16

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

Недопустимость

Типы, которые могут быть равны null, можно аннотировать с помощью @nullable . Более подробную информацию об аннотации nullable см. в разделе `nullable` .

Посылки, изготовленные на заказ

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

Вот пример декларации AIDL, допускающей отправку посылок:

    package my.pack.age;
    parcelable Foo;

По умолчанию это объявляет Java-объект Parcelable, где my.pack.age.Foo — это Java-класс, реализующий интерфейс Parcelable .

Для объявления пользовательского бэкенда CPP, который можно разделить на пакеты в AIDL, используйте cpp_header :

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

Реализация на C++ в файле my/pack/age/Foo.h выглядит следующим образом:

    #include <binder/Parcelable.h>

    class MyCustomParcelable : public android::Parcelable {
    public:
        status_t writeToParcel(Parcel* parcel) const override;
        status_t readFromParcel(const Parcel* parcel) override;

        std::string toString() const;
        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

Для объявления пользовательского модуля NDK, реализующего интерфейс Parcelable в AIDL, используйте ndk_header :

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

Реализация NDK в android/pack/age/Foo.h выглядит следующим образом:

    #include <android/binder_parcel.h>

    class MyCustomParcelable {
    public:

        binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
        binder_status_t readFromParcel(const AParcel* _Nonnull parcel);

        std::string toString() const;

        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

В Android 15 для объявления пользовательского объекта Rust Parcelable в AIDL используйте rust_type :

package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";

Реализация на Rust в rust_crate/src/lib.rs выглядит следующим образом:

use binder::{
    binder_impl::{BorrowedParcel, UnstructuredParcelable},
    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
    StatusCode,
};

#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
    pub bar: String,
}

impl UnstructuredParcelable for Foo {
    fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
        parcel.write(&self.bar)?;
        Ok(())
    }

    fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
        let bar = parcel.read()?;
        Ok(Self { bar })
    }
}

impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);

Затем вы можете использовать этот парсируемый объект в качестве типа в файлах AIDL, но он не будет генерироваться AIDL. Предоставьте операторы < и == для пользовательских парсируемых объектов в бэкенде CPP и NDK, чтобы использовать их в union .

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

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

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

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

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

Профсоюзы

Объединения AIDL помечены тегами, и их характеристики схожи во всех бэкэндах. Они формируются на основе значения по умолчанию первого поля, и для взаимодействия с ними существует специфический для каждого языка способ:

    union Foo {
      int intField;
      long longField;
      String stringField;
      MyParcelable parcelableField;
      ...
    }

Пример на Java

    Foo u = Foo.intField(42);              // construct

    if (u.getTag() == Foo.intField) {      // tag query
      // use u.getIntField()               // getter
    }

    u.setStringField("abc");               // setter

Пример использования C++ и NDK

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

Пример на Rust

В Rust объединения реализованы как перечисления (enums) и не имеют явных геттеров и сеттеров.

    let mut u = Foo::Default();              // default constructor
    match u {                                // tag match + get
      Foo::IntField(x) => assert!(x == 0);
      Foo::LongField(x) => panic!("Default constructed to first field");
      Foo::StringField(x) => panic!("Default constructed to first field");
      Foo::ParcelableField(x) => panic!("Default constructed to first field");
      ...
    }
    u = Foo::StringField("abc".to_string()); // set

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

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

Выходные параметры с ошибками

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

Какие значения ошибок использовать

Многие встроенные значения ошибок можно использовать в любых интерфейсах AIDL, но некоторые обрабатываются особым образом. Например, EX_UNSUPPORTED_OPERATION и EX_ILLEGAL_ARGUMENT допустимы для использования, если они описывают условие ошибки, но EX_TRANSACTION_FAILED использовать нельзя, поскольку он обрабатывается особым образом базовой инфраструктурой. Для получения дополнительной информации об этих встроенных значениях обратитесь к определениям, специфичным для бэкэнда.

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

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

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

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

Бэкенд Определение
Java 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» .

Типы импорта

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

import my.package.IFoo;

Или в бэкэнде на языке C++:

#include <my/package/IFoo.h>

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

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

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

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

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

Реализуйте интерфейс.

Для реализации интерфейса необходимо наследовать от нативного класса-заглушки. Реализация интерфейса часто называется сервисом , когда она зарегистрирована в диспетчере служб или android.app.ActivityManager , и называется колбэком, когда она зарегистрирована клиентом сервиса. Однако для описания реализаций интерфейсов используются различные названия в зависимости от конкретного применения. Класс-заглушка считывает команды от драйвера-связывателя и выполняет методы, которые вы реализуете. Представьте, что у вас есть AIDL-файл, подобный этому:

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

В Java необходимо наследовать сгенерированный класс- Stub :

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

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

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

Или с использованием асинхронного Rust:

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

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

    impl Interface for MyFoo {}

    #[async_trait]
    impl IFooAsyncServer for MyFoo {
        async fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

Зарегистрируйтесь и получите услуги

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

На языке Java:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // return if service is started now
    myService = IFoo.Stub.asInterface(ServiceManager.checkService("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"));

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

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // return if service is started now
    status_t err = checkService<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
    binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("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(ndk::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()
}

В асинхронном бэкенде Rust, с однопоточной средой выполнения:

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

#[tokio::main(flavor = "current_thread")]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleeps forever, but does not join the binder threadpool.
    // Spawned tasks run on this thread.
    std::future::pending().await
}

Одно важное отличие от других вариантов заключается в том, что при использовании асинхронного Rust и однопоточной среды выполнения не требуется вызывать join_thread_pool . Это связано с тем, что Tokio необходимо выделить поток для выполнения порожденных задач. В следующем примере для этой цели используется основной поток. Любые задачи, порожденные с помощью tokio::spawn выполняются в основном потоке.

В асинхронном бэкенде Rust с многопоточной средой выполнения:

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

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleep forever.
    tokio::task::block_in_place(|| {
        binder::ProcessState::join_thread_pool();
    });
}

В многопоточной среде выполнения Tokio порожденные задачи не выполняются в основном потоке. Поэтому целесообразнее вызывать join_thread_pool в основном потоке, чтобы он не простаивал. Для выхода из асинхронного контекста необходимо обернуть вызов в block_in_place .

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

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

Информация о звонящем

При получении вызова ядра через Binder информация о вызывающем процессе доступна в нескольких API. Идентификатор процесса (PID) относится к идентификатору процесса Linux, отправляющего транзакцию. Идентификатор пользователя (UI) относится к идентификатору пользователя Linux. При получении одностороннего вызова PID вызывающего процесса равен 0. Вне контекста транзакции Binder эти функции возвращают PID и UID текущего процесса.

В бэкенде на Java:

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

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

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

В бэкэнде NDK:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

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

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

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

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

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

В бэкенде на 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 поддерживает WeakReference , она не поддерживает слабые ссылки на связывающие объекты на нативном уровне.

В бэкенде на языке C++ слабым типом является wp<IFoo> .

В бэкенде NDK используйте ScopedAIBinder_Weak :

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

В бэкенде на Rust используйте WpIBinder или Weak<IFoo> :

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

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

Дескриптор интерфейса определяет тип интерфейса. Это полезно при отладке или при работе с неизвестным связывателем.

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

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

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

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

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

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

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

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

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

    #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, итерация предоставляется следующим образом.

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

    ::android::enum_range<MyEnum>()

В бэкэнде NDK:

   ::ndk::enum_range<MyEnum>()

В бэкенде на Rust:

    MyEnum::enum_values()

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

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

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

При использовании асинхронного бэкенда Rust вам потребуется два пула потоков: binder и Tokio. Это означает, что приложения, использующие асинхронный Rust, требуют особого подхода, особенно в отношении использования join_thread_pool . Более подробную информацию об этом см. в разделе о регистрации сервисов .

Зарезервированные имена

В C++, Java и Rust некоторые имена зарезервированы в качестве ключевых слов или для использования в зависимости от языка. Хотя AIDL не устанавливает ограничений на основе языковых правил, использование имен полей или типов, совпадающих с зарезервированным именем, может привести к ошибке компиляции в C++ или Java. В Rust поле или тип переименовывается с использованием синтаксиса необработанного идентификатора, доступного с помощью префикса r# .

Мы рекомендуем по возможности избегать использования зарезервированных имен в определениях AIDL, чтобы предотвратить неудобные привязки или полные ошибки компиляции.

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

Имена, которых следует избегать:

,

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

В приведенной ниже таблице стабильность API-интерфейса относится к возможности компиляции кода с использованием этого API-интерфейса таким образом, чтобы код мог распространяться независимо от бинарного файла system.img libbinder.so .

В AIDL используются следующие бэкэнды:

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

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

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

Базовая система сборки

В любом Android.bp module cc_ или java_ (или в их эквивалентах Android.mk ) можно указать файлы AIDL ( .aidl ) в качестве исходных файлов. В этом случае используются бэкенды AIDL на Java или C++ (а не на NDK), и классы, использующие соответствующие файлы AIDL, автоматически добавляются в модуль. В этих модулях в группе aidl: можно указать такие параметры, как local_include_dirs (который указывает системе сборки корневой путь к файлам AIDL в этом модуле).

Бэкенд Rust предназначен только для использования с Rust. Модули rust_ обрабатываются иначе: файлы AIDL не указываются в качестве исходных файлов. Вместо этого модуль aidl_interface создает библиотеку rustlib с именем aidl_interface_name -rust , с которой можно выполнить компоновку. Подробности см. в примере Rust AIDL .

aidl_interface

Типы, используемые в системе сборки aidl_interface должны быть структурированными. Для того чтобы быть структурированными, парселблейбы должны содержать поля напрямую, а не быть объявлениями типов, определенных непосредственно в целевых языках. О том, как структурированный AIDL соотносится со стабильным AIDL, см. раздел «Структурированный AIDL против стабильного AIDL» .

Типы

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

Java или тип AIDL тип C++ Тип НДК Тип ржавчины
boolean bool bool bool
byte 8 int8_t int8_t i8
char char16_t char16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
float float float f32
double double double f64
String android::String16 std::string В: &str
Выход: String
android.os.Parcelable android::Parcelable Н/Д Н/Д
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder 9
T[] std::vector<T> std::vector<T> В: &[T]
Выход: Vec<T>
byte[] std::vector std::vector 1 В: &[u8]
Выход: Vec<u8>
List<T> std::vector<T> 2 std::vector<T> 3 В: In: &[T] 4
Выход: Vec<T>
FileDescriptor android::base::unique_fd Н/Д Н/Д
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor 9
Тип интерфейса ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong<dyn T> 9
Тип посылки ( T ) T T T 9
Тип соединения ( T ) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

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

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

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

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

5. В Android 12 и более поздних версиях поддерживаются типы объединения.

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

7. Для создания объекта SharedRefBase для связывателя используйте SharedRefBase::make\<My\>(... args ...) . Эта функция создает объект std::shared_ptr\<T\> , который также управляется внутри системы, если связыватель принадлежит другому процессу. Создание объекта другими способами приводит к двойному владению.

8. См. также Java или AIDL тип byte[] .

9. В Rust типы, не реализующие Default ( IBinder , ParcelFileDescriptor , интерфейсные типы и неструктурированные парсируемые объекты), отображаются в Option<T> когда требуется значение по умолчанию (например, в парсируемых полях, out параметрах и массивах фиксированного размера в этих контекстах), даже без аннотации @nullable . Недопустимость значения null по-прежнему обеспечивается во время выполнения при парсинге, возвращая StatusCode::UNEXPECTED_NULL если значение равно None . Для возвращаемых значений методов, in параметрах (передаваемых как &T ) и inout параметрах (передаваемых как &mut T ), эти типы не оборачиваются в Option<T> если не аннотированы @nullable .

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

При указании типов аргументов функций можно использовать значения in , out или inout . Это определяет направление передачи информации при вызове межпроцессного взаимодействия (IPC).

  • Спецификатор in указывает, что данные передаются от вызывающей стороны к вызываемой. Спецификатор in определяет направление передачи по умолчанию, но если типы данных также могут передаваться out , то необходимо указать направление.

  • Спецификатор out аргумента означает, что данные передаются от вызываемой функции к вызывающей.

  • Спецификатор аргумента inout представляет собой комбинацию обоих этих вариантов. Однако мы рекомендуем избегать использования спецификатора аргумента inout . Если вы используете inout с версионированным интерфейсом и более старой вызываемой функцией, дополнительные поля, присутствующие только в вызывающей функции, сбрасываются до значений по умолчанию. Что касается Rust, обычный тип inout получает &mut T , а тип inout типа списка получает &mut Vec<T> .

interface IRepeatExamples {
    MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
    MyParcelable RepeatParcelableWithIn(in MyParcelable token);
    void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
    void RepeatParcelableWithInOut(inout MyParcelable param);
}

UTF-8 и UTF-16

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

Недопустимость

Типы, которые могут быть равны null, можно аннотировать с помощью @nullable . Более подробную информацию об аннотации nullable см. в разделе `nullable` .

Посылки, изготовленные на заказ

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

Вот пример декларации AIDL, допускающей отправку посылок:

    package my.pack.age;
    parcelable Foo;

По умолчанию это объявляет Java-объект Parcelable, где my.pack.age.Foo — это Java-класс, реализующий интерфейс Parcelable .

Для объявления пользовательского бэкенда CPP, который можно разделить на пакеты в AIDL, используйте cpp_header :

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

Реализация на C++ в файле my/pack/age/Foo.h выглядит следующим образом:

    #include <binder/Parcelable.h>

    class MyCustomParcelable : public android::Parcelable {
    public:
        status_t writeToParcel(Parcel* parcel) const override;
        status_t readFromParcel(const Parcel* parcel) override;

        std::string toString() const;
        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

Для объявления пользовательского модуля NDK, реализующего интерфейс Parcelable в AIDL, используйте ndk_header :

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

Реализация NDK в android/pack/age/Foo.h выглядит следующим образом:

    #include <android/binder_parcel.h>

    class MyCustomParcelable {
    public:

        binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
        binder_status_t readFromParcel(const AParcel* _Nonnull parcel);

        std::string toString() const;

        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

В Android 15 для объявления пользовательского объекта Rust Parcelable в AIDL используйте rust_type :

package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";

Реализация на Rust в rust_crate/src/lib.rs выглядит следующим образом:

use binder::{
    binder_impl::{BorrowedParcel, UnstructuredParcelable},
    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
    StatusCode,
};

#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
    pub bar: String,
}

impl UnstructuredParcelable for Foo {
    fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
        parcel.write(&self.bar)?;
        Ok(())
    }

    fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
        let bar = parcel.read()?;
        Ok(Self { bar })
    }
}

impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);

Затем вы можете использовать этот парсируемый объект в качестве типа в файлах AIDL, но он не будет генерироваться AIDL. Предоставьте операторы < и == для пользовательских парсируемых объектов в бэкенде CPP и NDK, чтобы использовать их в union .

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

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

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

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

In other backends, fields are initialized with default initialized values when default values aren't defined. For example, in the C++ backend, String fields are initialized as an empty string and List<T> fields are initialized as an empty vector<T> . @nullable fields are initialized as null-value fields.

Профсоюзы

AIDL unions are tagged and their features are similar in all backends. They're constructed to the first field's default value and they have a language-specific way to interact with them:

    union Foo {
      int intField;
      long longField;
      String stringField;
      MyParcelable parcelableField;
      ...
    }

Пример на Java

    Foo u = Foo.intField(42);              // construct

    if (u.getTag() == Foo.intField) {      // tag query
      // use u.getIntField()               // getter
    }

    u.setStringField("abc");               // setter

C++ and NDK example

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

Rust example

In Rust, unions are implemented as enums and don't have explicit getters and setters.

    let mut u = Foo::Default();              // default constructor
    match u {                                // tag match + get
      Foo::IntField(x) => assert!(x == 0);
      Foo::LongField(x) => panic!("Default constructed to first field");
      Foo::StringField(x) => panic!("Default constructed to first field");
      Foo::ParcelableField(x) => panic!("Default constructed to first field");
      ...
    }
    u = Foo::StringField("abc".to_string()); // set

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

The Android OS provides built-in error types for services to use when reporting errors. These are used by binders and can be used by any services implementing a binder interface. Their use is well documented in the AIDL definition and they don't require any user-defined status or return type.

Output parameters with errors

When an AIDL function reports an error, the function might not initialize or modify output parameters. Specifically, output parameters might be modified if the error occurs during unparceling, as opposed to happening during the processing of the transaction itself. In general, when getting an error from an AIDL function, all inout and out parameters as well as the return value (which acts like an out parameter in some backends) should be considered to be in an indefinite state.

Which error values to use

Many of the built-in error values can be used in any AIDL interfaces, but some are treated in a special way. For example, EX_UNSUPPORTED_OPERATION and EX_ILLEGAL_ARGUMENT are OK to use when they describe the error condition, but EX_TRANSACTION_FAILED must not be used because it's treated specially by the underlying infrastructure. Check the backend specific definitions for more information on these built-in values.

If the AIDL interface requires additional error values that aren't covered by the built-in error types, they can use the special service-specific built-in error that allows the inclusion of a service-specific error value that's defined by the user. These service-specific errors are typically defined in the AIDL interface as a const int or int -backed enum and aren't parsed by binder.

In Java, errors map to exceptions, such as android.os.RemoteException . For service-specific exceptions, Java uses android.os.ServiceSpecificException along with the user-defined error.

Native code in Android doesn't use exceptions. The CPP backend uses android::binder::Status . The NDK backend uses ndk::ScopedAStatus . Every method generated by AIDL returns one of these, representing the status of the method. The Rust backend uses the same exception code values as the NDK, but converts them into native Rust errors ( StatusCode , ExceptionCode ) before delivering them to the user. For service-specific errors, the returned Status or ScopedAStatus uses EX_SERVICE_SPECIFIC along with the user-defined error.

The built-in error types can be found in the following files:

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

Use various backends

These instructions are specific to Android platform code. These examples use a defined type, my.package.IFoo . For instructions on how to use the Rust backend, see the Rust AIDL example in Android Rust patterns .

Import types

Whether the defined type is an interface, parcelable, or union, you can import it in Java:

import my.package.IFoo;

Or in the CPP backend:

#include <my/package/IFoo.h>

Or in the NDK backend (notice the extra aidl namespace):

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

Or in the Rust backend:

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

Although you can import a nested type in Java, in the CPP and NDK backends you must include the header for its root type. For example, when importing a nested type Bar defined in my/package/IFoo.aidl ( IFoo is the root type of the file) you must include <my/package/IFoo.h> for the CPP backend (or <aidl/my/package/IFoo.h> for the NDK backend).

Implement an interface

To implement an interface, you must inherit from the native stub class. An implementation of an interface is often called a service when it's registered with the service manager or android.app.ActivityManager and called a callback when it's registered by a client of a service. However, a variety of names are used to describe interface implementations depending on the exact usage. The stub class reads commands from the binder driver and executes the methods that you implement. Imagine that you have an AIDL file like this:

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

In Java, you must extend from the generated Stub class:

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

In the CPP backend:

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

In the NDK backend (notice the extra aidl namespace):

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

In the Rust backend:

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

Or with async Rust:

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

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

    impl Interface for MyFoo {}

    #[async_trait]
    impl IFooAsyncServer for MyFoo {
        async fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

Register and get services

Services in platform Android are usually registered with the servicemanager process. In addition to the following APIs, some APIs check the service (meaning they return immediately if the service isn't available). Check the corresponding servicemanager interface for exact details. You can perform these operations only when compiling against platform Android.

In Java:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // return if service is started now
    myService = IFoo.Stub.asInterface(ServiceManager.checkService("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"));

In the CPP backend:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // return if service is started now
    status_t err = checkService<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"));

In the NDK backend (notice the extra aidl namespace):

    #include <android/binder_manager.h>
    // registering
    binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("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(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));

In the Rust backend:

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

In the async Rust backend, with a single-threaded runtime:

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

#[tokio::main(flavor = "current_thread")]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleeps forever, but does not join the binder threadpool.
    // Spawned tasks run on this thread.
    std::future::pending().await
}

One important difference from the other options is that you don't call join_thread_pool when using async Rust and a single-threaded runtime. This is because you need to give Tokio a thread where it can execute spawned tasks. In the following example, the main thread serves that purpose. Any tasks spawned using tokio::spawn execute on the main thread.

In the async Rust backend, with a multithreaded runtime:

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

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleep forever.
    tokio::task::block_in_place(|| {
        binder::ProcessState::join_thread_pool();
    });
}

With the multithreaded Tokio runtime, spawned tasks don't execute on the main thread. Therefore, it makes more sense to call join_thread_pool on the main thread so that the main thread isn't idle. You must wrap the call in block_in_place to leave the async context.

You can request to get a notification for when a service hosting a binder dies. This can help to avoid leaking callback proxies or assist in error recovery. Make these calls on binder proxy objects.

  • In Java, use android.os.IBinder::linkToDeath .
  • In the CPP backend, use android::IBinder::linkToDeath .
  • In the NDK backend, use AIBinder_linkToDeath . Always use AIBinder_DeathRecipient_setOnUnlinked to control the lifetime of your death recipient cookie.
  • In the Rust backend, create a DeathRecipient object, then call my_binder.link_to_death(&mut my_death_recipient) . Note that because DeathRecipient owns the callback, you must keep that object alive as long as you want to receive notifications.

Caller information

When receiving a kernel binder call, caller information is available in several APIs. The process ID (PID) refers to the Linux process ID of the process that's sending a transaction. The user ID (UI) refers to the Linux user ID. When receiving a one-way call, the calling PID is 0. Outside of a binder transaction context, these functions return the PID and UID of the current process.

In the Java backend:

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

In the CPP backend:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

In the NDK backend:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

Bug reports and debugging API for services

When bug reports run (for example, with adb bugreport ), they collect information from all around the system to aid with debugging various issues. For AIDL services, bug reports use the binary dumpsys on all services registered with the service manager to dump their information into the bug report. You can also use dumpsys on the command line to get information from a service with dumpsys SERVICE [ARGS] . In the C++ and Java backends, you can control the order in which services get dumped by using additional arguments to addService . You can also use dumpsys --pid SERVICE to get the PID of a service while debugging.

To add custom output to your service, override the dump method in your server object like you're implementing any other IPC method defined in an AIDL file. When doing this, restrict dumping to the app permission android.permission.DUMP or restrict dumping to specific UIDs.

In the Java backend:

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

In the CPP backend:

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

In the NDK backend:

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

In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):

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

Use weak pointers

You can hold a weak reference to a binder object.

While Java supports WeakReference , it doesn't support weak binder references at the native layer.

In the CPP backend, the weak type is wp<IFoo> .

In the NDK backend, use ScopedAIBinder_Weak :

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

In the Rust backend, use WpIBinder or Weak<IFoo> :

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

Dynamically get interface descriptor

The interface descriptor identifies the type of an interface. This is useful when debugging or when you have an unknown binder.

In Java, you can get the interface descriptor with code such as:

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

In the CPP backend:

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

The NDK and Rust backends don't support this capability.

Statically get interface descriptor

Sometimes (such as when registering @VintfStability services), you need to know what the interface descriptor is statically. In Java, you can get the descriptor by adding code such as:

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

In the CPP backend:

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

In the NDK backend (notice the extra aidl namespace):

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

In the Rust backend:

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

Enum range

In native backends, you can iterate over the possible values an enum can take on. Due to code size considerations, this isn't supported in Java.

For an enum MyEnum defined in AIDL, iteration is provided as follows.

In the CPP backend:

    ::android::enum_range<MyEnum>()

In the NDK backend:

   ::ndk::enum_range<MyEnum>()

In the Rust backend:

    MyEnum::enum_values()

Thread management

Every instance of libbinder in a process maintains one threadpool. For most use cases, this should be exactly one threadpool, shared across all backends. The only exception is if vendor code loads another copy of libbinder to talk to /dev/vndbinder . This is on a separate binder node, so the threadpool isn't shared.

For the Java backend, the threadpool can only increase in size (because it's already started):

    BinderInternal.setMaxThreads(<new larger value>);

For the CPP backend, the following operations are available:

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

Similarly, in the NDK backend:

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

In the Rust backend:

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

With the async Rust backend, you need two threadpools: binder and Tokio. This means that apps using async Rust need special considerations, especially when it comes to the use of join_thread_pool . See the section on registering services for more information on this.

Reserved names

C++, Java, and Rust reserve some names as keywords or for language-specific use. While AIDL doesn't enforce restrictions based on language rules, using field or type names that match a reserved name can result in a compilation failure for C++ or Java. For Rust, the field or type is renamed using the raw identifier syntax, accessible using the r# prefix.

We recommend avoiding using reserved names in your AIDL definitions where possible to avoid unergonomic bindings or outright compilation failure.

If you already have reserved names in your AIDL definitions, you can safely rename fields while remaining protocol compatible. You might need to update your code to continue building, but any already built programs continue to interoperate.

Names to avoid:

,

An AIDL backend is a target for stub code generation. Always use AIDL files in a particular language with a specific runtime. Depending on the context, you should use different AIDL backends.

In the following table, the stability of the API surface refers to the ability to compile code against this API surface in a way that the code can be delivered independently from the system.img libbinder.so binary.

AIDL has the following backends:

Бэкенд Язык API surface Build systems
Java Java SDK or SystemApi (stable*) Все
NDK C++ libbinder_ndk (stable*) aidl_interface
CPP C++ libbinder (unstable) Все
Ржавчина Ржавчина libbinder_rs (stable*) aidl_interface
  • These API surfaces are stable, but many of the APIs, such as those for service management, are reserved for internal platform use and aren't available to apps. For more information on how to use AIDL in apps, see Android Interface Definition Language (AIDL) .
  • The Rust backend was introduced in Android 12; the NDK backend has been available as of Android 10.
  • The Rust crate is built on top of libbinder_ndk , which lets it be stable and portable. APEXes use the binder crate in the standard way on the system side. The Rust portion is bundled into an APEX and shipped inside it. This portion depends on the libbinder_ndk.so on the system partition.

Build systems

Depending on the backend, there are two ways to compile AIDL into stub code. For more details on the build systems, see Soong Modules Reference .

Core build system

In any cc_ or java_ Android.bp module (or in their Android.mk equivalents), you can specify AIDL ( .aidl ) files as source files. In this case, the Java or CPP backends of AIDL are used (not the NDK backend), and the classes to use the corresponding AIDL files are added to the module automatically. You can specify options such as local_include_dirs (which tells the build system the root path to AIDL files in that module) in these modules under an aidl: group.

The Rust backend is only for use with Rust. rust_ modules are handled differently in that AIDL files aren't specified as source files. Instead, the aidl_interface module produces a rustlib called aidl_interface_name -rust , which can be linked against. For details, see the Rust AIDL example .

aidl_interface

Types used with the aidl_interface build system must be structured. In order to be structured, parcelables must contain fields directly and not be declarations of types defined directly in target languages. For how structured AIDL fits in with stable AIDL, see Structured versus stable AIDL .

Типы

Consider the aidl compiler as a reference implementation for types. When you create an interface, invoke aidl --lang=<backend> ... to see the resulting interface file. When you use the aidl_interface module, you can view the output in out/soong/.intermediates/ <path to module> / .

Java or AIDL type C++ type NDK type Rust type
boolean bool bool bool
byte 8 int8_t int8_t i8
char char16_t char16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
float float float f32
double double double f64
String android::String16 std::string In: &str
Out: String
android.os.Parcelable android::Parcelable Н/Д Н/Д
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder 9
T[] std::vector<T> std::vector<T> In: &[T]
Out: Vec<T>
byte[] std::vector std::vector 1 In: &[u8]
Out: Vec<u8>
List<T> std::vector<T> 2 std::vector<T> 3 In: In: &[T] 4
Out: Vec<T>
FileDescriptor android::base::unique_fd Н/Д Н/Д
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor 9
Interface type ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong<dyn T> 9
Parcelable type ( T ) T T T 9
Union type ( T ) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

1. In Android 12 or higher, byte arrays use uint8_t instead of int8_t for compatibility reasons.

2. The C++ backend supports List<T> where T is one of String , IBinder , ParcelFileDescriptor or parcelable. In Android 13 or higher, T can be any nonprimitive type (including interface types) except arrays. AOSP recommends using array types like T[] , because they work in all backends.

3. The NDK backend supports List<T> where T is one of String , ParcelFileDescriptor or parcelable. In Android 13 or higher, T can be any nonprimitive type except arrays.

4. Types are passed differently for Rust code depending on whether they are input (an argument), or an output (a returned value).

5. Union types are supported in Android 12 and higher.

6. In Android 13 or higher, fixed-size arrays are supported. Fixed-size arrays can have multiple dimensions (for example, int[3][4] ). In the Java backend, fixed-size arrays are represented as array types.

7. To instantiate a binder SharedRefBase object, use SharedRefBase::make\<My\>(... args ...) . This function creates a std::shared_ptr\<T\> object, which is also managed internally, in case the binder is owned by another process. Creating the object other ways causes double ownership.

8. See also Java or AIDL type byte[] .

9. In Rust, types that don't implement Default ( IBinder , ParcelFileDescriptor , interface types, and unstructured parcelables) map to Option<T> when a default value is required (such as in parcelable fields, out parameters, and fixed-size arrays in those contexts), even without the @nullable annotation. Non-nullability is still enforced at runtime during parceling, returning StatusCode::UNEXPECTED_NULL if the value is None . For method return values, in parameters (passed as &T ), and inout parameters (passed as &mut T ), these types aren't wrapped in Option<T> unless annotated with @nullable .

Directionality (in, out, and inout)

When specifying the types of the arguments to functions, you can specify them as in , out , or inout . This controls the direction that information is passed for an IPC call.

  • The in argument specifier indicates data is passed from the caller to the callee. The in specifier is the default direction, but if data types can also be out , then you must specify the direction.

  • The out argument specifier means that data is passed from the callee to the caller.

  • The inout argument specifier is the combination of both of these. However, we recommend avoiding using the argument specifier inout . If you use inout with a versioned interface and an older callee, the additional fields that are present only in the caller get reset to their default values. With respect to Rust, a normal inout type receives &mut T , and a list inout type receives &mut Vec<T> .

interface IRepeatExamples {
    MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
    MyParcelable RepeatParcelableWithIn(in MyParcelable token);
    void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
    void RepeatParcelableWithInOut(inout MyParcelable param);
}

UTF-8 and UTF-16

With the CPP backend, you can choose whether strings are UTF-8 or UTF-16. Declare strings as @utf8InCpp String in AIDL to automatically convert them to UTF-8. The NDK and Rust backends always use UTF-8 strings. For more information about the utf8InCpp annotation, see utf8InCpp .

Nullability

You can annotate types that can be null with @nullable . For more information about the nullable annotation, see nullable .

Custom parcelables

A custom parcelable is a parcelable that's implemented manually in a target backend. Use custom parcelables only when you're trying to add support to other languages for an existing custom parcelable which can't be changed.

Here's an example of an AIDL parcelable declaration:

    package my.pack.age;
    parcelable Foo;

By default, this declares a Java parcelable where my.pack.age.Foo is a Java class implementing the Parcelable interface.

For a declaration of a custom CPP backend parcelable in AIDL, use cpp_header :

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

The C++ implementation in my/pack/age/Foo.h looks like this:

    #include <binder/Parcelable.h>

    class MyCustomParcelable : public android::Parcelable {
    public:
        status_t writeToParcel(Parcel* parcel) const override;
        status_t readFromParcel(const Parcel* parcel) override;

        std::string toString() const;
        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

For a declaration of a custom NDK parcelable in AIDL, use ndk_header :

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

The NDK implementation in android/pack/age/Foo.h looks like this:

    #include <android/binder_parcel.h>

    class MyCustomParcelable {
    public:

        binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
        binder_status_t readFromParcel(const AParcel* _Nonnull parcel);

        std::string toString() const;

        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

In Android 15, for declaration of a custom Rust parcelable in AIDL, use rust_type :

package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";

The Rust implementation in rust_crate/src/lib.rs looks like this:

use binder::{
    binder_impl::{BorrowedParcel, UnstructuredParcelable},
    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
    StatusCode,
};

#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
    pub bar: String,
}

impl UnstructuredParcelable for Foo {
    fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
        parcel.write(&self.bar)?;
        Ok(())
    }

    fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
        let bar = parcel.read()?;
        Ok(Self { bar })
    }
}

impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);

Then you can use this parcelable as a type in AIDL files, but it won't be generated by AIDL. Provide < and == operators for CPP and NDK backend custom parcelables to use them in union .

Default values

Structured parcelables can declare per-field default values for primitives, String fields, and arrays of these types.

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

In the Java backend, when default values are missing, fields are initialized as zero values for primitive types and null for nonprimitive types.

In other backends, fields are initialized with default initialized values when default values aren't defined. For example, in the C++ backend, String fields are initialized as an empty string and List<T> fields are initialized as an empty vector<T> . @nullable fields are initialized as null-value fields.

Профсоюзы

AIDL unions are tagged and their features are similar in all backends. They're constructed to the first field's default value and they have a language-specific way to interact with them:

    union Foo {
      int intField;
      long longField;
      String stringField;
      MyParcelable parcelableField;
      ...
    }

Пример на Java

    Foo u = Foo.intField(42);              // construct

    if (u.getTag() == Foo.intField) {      // tag query
      // use u.getIntField()               // getter
    }

    u.setStringField("abc");               // setter

C++ and NDK example

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

Rust example

In Rust, unions are implemented as enums and don't have explicit getters and setters.

    let mut u = Foo::Default();              // default constructor
    match u {                                // tag match + get
      Foo::IntField(x) => assert!(x == 0);
      Foo::LongField(x) => panic!("Default constructed to first field");
      Foo::StringField(x) => panic!("Default constructed to first field");
      Foo::ParcelableField(x) => panic!("Default constructed to first field");
      ...
    }
    u = Foo::StringField("abc".to_string()); // set

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

The Android OS provides built-in error types for services to use when reporting errors. These are used by binders and can be used by any services implementing a binder interface. Their use is well documented in the AIDL definition and they don't require any user-defined status or return type.

Output parameters with errors

When an AIDL function reports an error, the function might not initialize or modify output parameters. Specifically, output parameters might be modified if the error occurs during unparceling, as opposed to happening during the processing of the transaction itself. In general, when getting an error from an AIDL function, all inout and out parameters as well as the return value (which acts like an out parameter in some backends) should be considered to be in an indefinite state.

Which error values to use

Many of the built-in error values can be used in any AIDL interfaces, but some are treated in a special way. For example, EX_UNSUPPORTED_OPERATION and EX_ILLEGAL_ARGUMENT are OK to use when they describe the error condition, but EX_TRANSACTION_FAILED must not be used because it's treated specially by the underlying infrastructure. Check the backend specific definitions for more information on these built-in values.

If the AIDL interface requires additional error values that aren't covered by the built-in error types, they can use the special service-specific built-in error that allows the inclusion of a service-specific error value that's defined by the user. These service-specific errors are typically defined in the AIDL interface as a const int or int -backed enum and aren't parsed by binder.

In Java, errors map to exceptions, such as android.os.RemoteException . For service-specific exceptions, Java uses android.os.ServiceSpecificException along with the user-defined error.

Native code in Android doesn't use exceptions. The CPP backend uses android::binder::Status . The NDK backend uses ndk::ScopedAStatus . Every method generated by AIDL returns one of these, representing the status of the method. The Rust backend uses the same exception code values as the NDK, but converts them into native Rust errors ( StatusCode , ExceptionCode ) before delivering them to the user. For service-specific errors, the returned Status or ScopedAStatus uses EX_SERVICE_SPECIFIC along with the user-defined error.

The built-in error types can be found in the following files:

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

Use various backends

These instructions are specific to Android platform code. These examples use a defined type, my.package.IFoo . For instructions on how to use the Rust backend, see the Rust AIDL example in Android Rust patterns .

Import types

Whether the defined type is an interface, parcelable, or union, you can import it in Java:

import my.package.IFoo;

Or in the CPP backend:

#include <my/package/IFoo.h>

Or in the NDK backend (notice the extra aidl namespace):

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

Or in the Rust backend:

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

Although you can import a nested type in Java, in the CPP and NDK backends you must include the header for its root type. For example, when importing a nested type Bar defined in my/package/IFoo.aidl ( IFoo is the root type of the file) you must include <my/package/IFoo.h> for the CPP backend (or <aidl/my/package/IFoo.h> for the NDK backend).

Implement an interface

To implement an interface, you must inherit from the native stub class. An implementation of an interface is often called a service when it's registered with the service manager or android.app.ActivityManager and called a callback when it's registered by a client of a service. However, a variety of names are used to describe interface implementations depending on the exact usage. The stub class reads commands from the binder driver and executes the methods that you implement. Imagine that you have an AIDL file like this:

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

In Java, you must extend from the generated Stub class:

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

In the CPP backend:

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

In the NDK backend (notice the extra aidl namespace):

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

In the Rust backend:

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

Or with async Rust:

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

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

    impl Interface for MyFoo {}

    #[async_trait]
    impl IFooAsyncServer for MyFoo {
        async fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

Register and get services

Services in platform Android are usually registered with the servicemanager process. In addition to the following APIs, some APIs check the service (meaning they return immediately if the service isn't available). Check the corresponding servicemanager interface for exact details. You can perform these operations only when compiling against platform Android.

In Java:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // return if service is started now
    myService = IFoo.Stub.asInterface(ServiceManager.checkService("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"));

In the CPP backend:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // return if service is started now
    status_t err = checkService<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"));

In the NDK backend (notice the extra aidl namespace):

    #include <android/binder_manager.h>
    // registering
    binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("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(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));

In the Rust backend:

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

In the async Rust backend, with a single-threaded runtime:

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

#[tokio::main(flavor = "current_thread")]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleeps forever, but does not join the binder threadpool.
    // Spawned tasks run on this thread.
    std::future::pending().await
}

One important difference from the other options is that you don't call join_thread_pool when using async Rust and a single-threaded runtime. This is because you need to give Tokio a thread where it can execute spawned tasks. In the following example, the main thread serves that purpose. Any tasks spawned using tokio::spawn execute on the main thread.

In the async Rust backend, with a multithreaded runtime:

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

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleep forever.
    tokio::task::block_in_place(|| {
        binder::ProcessState::join_thread_pool();
    });
}

With the multithreaded Tokio runtime, spawned tasks don't execute on the main thread. Therefore, it makes more sense to call join_thread_pool on the main thread so that the main thread isn't idle. You must wrap the call in block_in_place to leave the async context.

You can request to get a notification for when a service hosting a binder dies. This can help to avoid leaking callback proxies or assist in error recovery. Make these calls on binder proxy objects.

  • In Java, use android.os.IBinder::linkToDeath .
  • In the CPP backend, use android::IBinder::linkToDeath .
  • In the NDK backend, use AIBinder_linkToDeath . Always use AIBinder_DeathRecipient_setOnUnlinked to control the lifetime of your death recipient cookie.
  • In the Rust backend, create a DeathRecipient object, then call my_binder.link_to_death(&mut my_death_recipient) . Note that because DeathRecipient owns the callback, you must keep that object alive as long as you want to receive notifications.

Caller information

When receiving a kernel binder call, caller information is available in several APIs. The process ID (PID) refers to the Linux process ID of the process that's sending a transaction. The user ID (UI) refers to the Linux user ID. When receiving a one-way call, the calling PID is 0. Outside of a binder transaction context, these functions return the PID and UID of the current process.

In the Java backend:

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

In the CPP backend:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

In the NDK backend:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

Bug reports and debugging API for services

When bug reports run (for example, with adb bugreport ), they collect information from all around the system to aid with debugging various issues. For AIDL services, bug reports use the binary dumpsys on all services registered with the service manager to dump their information into the bug report. You can also use dumpsys on the command line to get information from a service with dumpsys SERVICE [ARGS] . In the C++ and Java backends, you can control the order in which services get dumped by using additional arguments to addService . You can also use dumpsys --pid SERVICE to get the PID of a service while debugging.

To add custom output to your service, override the dump method in your server object like you're implementing any other IPC method defined in an AIDL file. When doing this, restrict dumping to the app permission android.permission.DUMP or restrict dumping to specific UIDs.

In the Java backend:

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

In the CPP backend:

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

In the NDK backend:

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

In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):

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

Use weak pointers

You can hold a weak reference to a binder object.

While Java supports WeakReference , it doesn't support weak binder references at the native layer.

In the CPP backend, the weak type is wp<IFoo> .

In the NDK backend, use ScopedAIBinder_Weak :

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

In the Rust backend, use WpIBinder or Weak<IFoo> :

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

Dynamically get interface descriptor

The interface descriptor identifies the type of an interface. This is useful when debugging or when you have an unknown binder.

In Java, you can get the interface descriptor with code such as:

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

In the CPP backend:

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

The NDK and Rust backends don't support this capability.

Statically get interface descriptor

Sometimes (such as when registering @VintfStability services), you need to know what the interface descriptor is statically. In Java, you can get the descriptor by adding code such as:

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

In the CPP backend:

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

In the NDK backend (notice the extra aidl namespace):

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

In the Rust backend:

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

Enum range

In native backends, you can iterate over the possible values an enum can take on. Due to code size considerations, this isn't supported in Java.

For an enum MyEnum defined in AIDL, iteration is provided as follows.

In the CPP backend:

    ::android::enum_range<MyEnum>()

In the NDK backend:

   ::ndk::enum_range<MyEnum>()

In the Rust backend:

    MyEnum::enum_values()

Thread management

Every instance of libbinder in a process maintains one threadpool. For most use cases, this should be exactly one threadpool, shared across all backends. The only exception is if vendor code loads another copy of libbinder to talk to /dev/vndbinder . This is on a separate binder node, so the threadpool isn't shared.

For the Java backend, the threadpool can only increase in size (because it's already started):

    BinderInternal.setMaxThreads(<new larger value>);

For the CPP backend, the following operations are available:

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

Similarly, in the NDK backend:

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

In the Rust backend:

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

With the async Rust backend, you need two threadpools: binder and Tokio. This means that apps using async Rust need special considerations, especially when it comes to the use of join_thread_pool . See the section on registering services for more information on this.

Reserved names

C++, Java, and Rust reserve some names as keywords or for language-specific use. While AIDL doesn't enforce restrictions based on language rules, using field or type names that match a reserved name can result in a compilation failure for C++ or Java. For Rust, the field or type is renamed using the raw identifier syntax, accessible using the r# prefix.

We recommend avoiding using reserved names in your AIDL definitions where possible to avoid unergonomic bindings or outright compilation failure.

If you already have reserved names in your AIDL definitions, you can safely rename fields while remaining protocol compatible. You might need to update your code to continue building, but any already built programs continue to interoperate.

Names to avoid: