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

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

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

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

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

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

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

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

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

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

helpl_interface

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

Типы

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

Тип Java или AIDL Тип С++ Тип НДК Тип ржавчины
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
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
Тип интерфейса ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong
Тип посылки ( T ) T T T
Тип соединения ( 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[] .

Направленность (внутрь, наружу и внутрь)

При указании типов аргументов функций вы можете указать их как 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

С помощью серверной части CPP вы можете выбрать, будут ли строки иметь формат 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, где 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, подлежащего пакетированию в 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 в 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 .

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

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

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

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

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

Союзы

Объединения 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.setSringField("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 объединения реализованы как перечисления и не имеют явных методов получения и установки.

    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 не использует исключений. Серверная часть CPP использует android::binder::Status . Серверная часть NDK использует ndk::ScopedAStatus . Каждый метод, сгенерированный AIDL, возвращает один из них, представляющий состояние метода. Бэкэнд Rust использует те же значения кодов исключений, что и NDK, но преобразует их в собственные ошибки Rust ( StatusCode , ExceptionCode ) перед доставкой пользователю. Для ошибок, специфичных для службы, возвращаемый Status или ScopedAStatus использует EX_SERVICE_SPECIFIC вместе с определяемой пользователем ошибкой.

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

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

Используйте различные бэкэнды

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

Типы импорта

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

import my.package.IFoo;

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

#include <my/package/IFoo.h>

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

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

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

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

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

Реализация интерфейса

Чтобы реализовать интерфейс, вы должны наследовать собственный класс-заглушку. Реализация интерфейса часто называется службой , когда она регистрируется в диспетчере служб или 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() { ... }
    }

В бэкэнде CPP:

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

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

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

В бэкэнде Rust:

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

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

    impl Interface for MyFoo {}

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

Или с асинхронным 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.

На Яве:

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

В бэкэнде CPP:

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

Одним из важных отличий от других вариантов является то, что вы не вызываете join_thread_pool при использовании асинхронного Rust и однопоточной среды выполнения. Это потому, что вам нужно предоставить 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 , чтобы выйти из асинхронного контекста.

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

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

Информация о вызывающем абоненте

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

В бэкэнде Java:

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

В бэкэнде CPP:

    ... = 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 в объекте сервера, как если бы вы реализовали любой другой метод IPC, определенный в файле AIDL. При этом ограничьте выгрузку разрешением приложения android.permission.DUMP или ограничьте выгрузку определенными UID.

В бэкэнде Java:

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

В бэкэнде CPP:

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

В бэкэнде NDK:

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

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

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

Используйте слабые указатели

Вы можете хранить слабую ссылку на объект привязки.

Хотя Java поддерживает WeakReference , она не поддерживает ссылки на слабые привязки на собственном уровне.

В серверной части CPP слабым типом является 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();

В бэкэнде CPP:

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

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

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

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

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

В бэкэнде CPP:

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

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

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

В бэкэнде Rust:

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

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

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

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

В бэкэнде CPP:

    ::android::enum_range<MyEnum>()

В бэкэнде NDK:

   ::ndk::enum_range<MyEnum>()

В бэкэнде Rust:

    MyEnum::enum_values()

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

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

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

    BinderInternal.setMaxThreads(<new larger value>);

Для серверной части CPP доступны следующие операции:

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

Аналогично в бэкэнде NDK:

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

В бэкэнде Rust:

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

Для асинхронного бэкэнда 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 Системы сборки
Ява Ява SDK или SystemApi (стабильная*) Все
НДК С++ libbinder_ndk (стабильная*) aidl_interface
CPP С++ libbinder (нестабильный) Все
Ржавчина Ржавчина libbinder_rs (стабильная*) aidl_interface
  • Эти поверхности API стабильны, но многие из API, например для управления службами, зарезервированы для внутреннего использования платформы и недоступны приложениям. Дополнительные сведения о том, как использовать AIDL в приложениях, см. в разделе Язык определения интерфейса Android (AIDL) .
  • Бэкэнд Rust был представлен в Android 12; Серверная часть NDK доступна начиная с Android 10.
  • Крейт Rust построен на основе libbinder_ndk , что делает его стабильным и переносимым. APEX использует ящик для подшивок стандартным образом на стороне системы. Часть Rust упакована в APEX и поставляется внутри него. Эта часть зависит от libbinder_ndk.so в системном разделе.

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

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

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

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

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

helpl_interface

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

Типы

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

Тип Java или AIDL Тип С++ Тип НДК Тип ржавчины
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
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
Тип интерфейса ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong
Тип посылки ( T ) T T T
Тип соединения ( 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[] .

Направленность (внутрь, наружу и внутрь)

При указании типов аргументов функций вы можете указать их как 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

С помощью серверной части CPP вы можете выбрать, будут ли строки иметь формат 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, где 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, подлежащего пакетированию в 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 в 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 .

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

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

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

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

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

Союзы

Объединения 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.setSringField("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 объединения реализованы как перечисления и не имеют явных методов получения и установки.

    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 как enum const int или int и не проанализированы с помощью связующего.

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

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

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

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

Используйте различные бэкэнды

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

Типы импорта

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

import my.package.IFoo;

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

#include <my/package/IFoo.h>

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

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

Или в бэкэнде ржавчины:

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

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

Реализовать интерфейс

Чтобы реализовать интерфейс, вы должны унаследовать от нативного класса Stub. Реализация интерфейса часто называется службой , когда он зарегистрирован в Service Manager или android.app.ActivityManager и называется обратным вызовом , когда он зарегистрирован клиентом службы. Тем не менее, различные имена используются для описания реализаций интерфейса в зависимости от точного использования. Класс STUB считывает команды от драйвера связующего и выполняет методы, которые вы реализуете. Представьте, что у вас есть файл AIDL, как это:

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

В Java вы должны простираться от сгенерированного класса Stub :

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

В бэкэнде CPP:

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

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

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

В бэкэнд ржавчины:

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

Или с асинхронной ржавкой:

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

В бэкэнде CPP:

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

В бэкэнд ржавчины:

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

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

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
}

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

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

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

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

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

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

Абонент информация

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

В бэкэнге Java:

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

В бэкэнде CPP:

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

В бэкэнд NDK:

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

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

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

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

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

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

В бэкэнге Java:

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

В бэкэнде CPP:

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

В бэкэнд NDK:

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

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

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

Используйте слабые указатели

Вы можете сохранить слабую ссылку на связующий объект.

В то время как Java поддерживает WeakReference , она не поддерживает слабые ссылки на переплет на местном уровне.

В бэкэнде CPP слабый тип - wp<IFoo> .

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

#include <android/binder_auto_utils.h>

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

В бэкэнге ржавчины используйте 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();

В бэкэнде CPP:

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

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

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

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

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

В бэкэнде CPP:

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

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

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

В бэкэнд ржавчины:

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

Enum диапазон

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

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

В бэкэнде CPP:

    ::android::enum_range<MyEnum>()

В бэкэнд NDK:

   ::ndk::enum_range<MyEnum>()

В бэкэнд ржавчины:

    MyEnum::enum_values()

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

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

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

    BinderInternal.setMaxThreads(<new larger value>);

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

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

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

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

В бэкэнд ржавчины:

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

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

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

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

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

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

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

,

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

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

У Aidl есть следующие бэкэнды:

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

Строительные системы

В зависимости от бэкэнда, есть два способа составить AIDL в код STUB. Для получения более подробной информации о системах сборки см. Ссылку на модули SOONG .

Система основной сборки

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

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

aidl_interface

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

Типы

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

Java или aidl -тип Тип C ++ Тип NDK Тип ржавчины
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
Out: String
android.os.Parcelable android::Parcelable Н/Д Н/Д
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> В: &[T]
OUT: Vec<T>
byte[] std::vector std::vector 1 В: &[u8]
OUT: Vec<u8>
List<T> std::vector<T> 2 std::vector<T> 3 В: In: &[T] 4
OUT: Vec<T>
FileDescriptor android::base::unique_fd Н/Д Н/Д
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
Тип интерфейса ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong
Приподнятый тип ( T ) T T T
Тип союза ( 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 или посылок. В Android 13 или выше T может быть любой непримидный тип (включая типы интерфейсов), кроме массивов. AOSP рекомендует использовать типы массивов, такие как T[] , потому что они работают во всех бэкэндах.

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

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

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

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

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

8. См. Также Java или Aidl Type byte[] .

Направленность (в, вне, и не в курсе)

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

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

  • Спецификатор out означает, что данные передаются из Callee Caller.

  • Спецификатор inout аргументации - это комбинация обоих из них. Тем не менее, мы рекомендуем избегать использования inout аргумента. Если вы используете inout с интерфейсом версии и более старой Callee, дополнительные поля, которые присутствуют только в вызывающем абоненте, получают сброс до значений по умолчанию. Что касается ржавчины, то нормальный тип inout получает &mut T , а также тип List 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

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

Нуль

Вы можете аннотировать типы, которые могут быть нулевыми с @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 в 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, для объявления пользовательской ржавчины, используемой в AIDL, используйте rust_type :

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

Реализация ржавчины в 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 Backend Custom Parcelables, чтобы использовать их в union .

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

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

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

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

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

Профсоюзы

Союзы 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.setSringField("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)

Ржавчина пример

В ржавчине профсоюзы реализованы как перечисления и не имеют явных сеттеров и сеттеров.

    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 как enum const int или int и не проанализированы с помощью связующего.

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

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

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

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

Используйте различные бэкэнды

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

Типы импорта

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

import my.package.IFoo;

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

#include <my/package/IFoo.h>

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

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

Или в бэкэнде ржавчины:

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

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

Реализовать интерфейс

Чтобы реализовать интерфейс, вы должны унаследовать от нативного класса Stub. Реализация интерфейса часто называется службой , когда он зарегистрирован в Service Manager или android.app.ActivityManager и называется обратным вызовом , когда он зарегистрирован клиентом службы. Тем не менее, различные имена используются для описания реализаций интерфейса в зависимости от точного использования. Класс STUB считывает команды от драйвера связующего и выполняет методы, которые вы реализуете. Представьте, что у вас есть файл AIDL, как это:

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

В Java вы должны простираться от сгенерированного класса Stub :

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

В бэкэнде CPP:

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

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

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

В бэкэнд ржавчины:

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

Или с асинхронной ржавкой:

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

В бэкэнде CPP:

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

В бэкэнд ржавчины:

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

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

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
}

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

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

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

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

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

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

Абонент информация

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

В бэкэнге Java:

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

В бэкэнде CPP:

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

В бэкэнд NDK:

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

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

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

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

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

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

В бэкэнге Java:

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

В бэкэнде CPP:

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

В бэкэнд NDK:

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

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

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

Используйте слабые указатели

Вы можете сохранить слабую ссылку на связующий объект.

В то время как Java поддерживает WeakReference , она не поддерживает слабые ссылки на переплет на местном уровне.

В бэкэнде CPP слабый тип - wp<IFoo> .

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

#include <android/binder_auto_utils.h>

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

В бэкэнге ржавчины используйте 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();

В бэкэнде CPP:

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

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

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

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

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

В бэкэнде CPP:

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

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

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

В бэкэнд ржавчины:

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

Enum диапазон

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

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

В бэкэнде CPP:

    ::android::enum_range<MyEnum>()

В бэкэнд NDK:

   ::ndk::enum_range<MyEnum>()

В бэкэнд ржавчины:

    MyEnum::enum_values()

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

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

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

    BinderInternal.setMaxThreads(<new larger value>);

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

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

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

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

В бэкэнд ржавчины:

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

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

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

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

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

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

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

,

Бэкэнд AIDL является целью для генерации кода заглушки. 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
Ява Ява 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
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
Interface type ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong
Parcelable type ( T ) T T T
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[] .

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.

Unions

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 example

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

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

    u.setSringField("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

Error handling

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:

Бэкэнд Определение
Ява 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 .
  • 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: