Бэкенды AIDL

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

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

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

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

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

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

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

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

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

aidl_interface

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

Типы

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

Тип Java или AIDL Тип C++ Тип NDK Тип Rust
boolean bool bool bool
byte8 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::SpIBinder9
T[] std::vector<T> std::vector<T> Вход: &[T]
Выход: Vec<T>
byte[] std::vector std::vector1 Вход: &[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::ParcelFileDescriptor9
Тип интерфейса (T) android::sp<T> std::shared_ptr<T>7 binder::Strong<dyn T>9
Тип Parcelable (T) T T T9
Тип объединения (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. Чтобы создать экземпляр объекта binder SharedRefBase, используйте SharedRefBase::make\<My\>(... args ...). Эта функция создает объект std::shared_ptr\<T\>, который также управляется внутри, если связыватель принадлежит другому процессу. Если создать объект другим способом, у него будет два владельца.

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

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

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

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

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

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

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

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

UTF-8 и UTF-16;

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

Допускает значение NULL

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

Специальные объекты Parcelable

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

Вот пример объявления parcelable в AIDL:

    package my.pack.age;
    parcelable Foo;

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

Чтобы объявить в AIDL объект Parcelable для пользовательского внутреннего пакета CPP, используйте 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);
    };

Чтобы объявить в AIDL пользовательский объект Parcelable NDK, используйте 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 пользовательского объекта parcelable, написанного на Rust, используйте 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);

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

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

Структурированные объекты Parcelable могут объявлять значения по умолчанию для примитивных типов, полей 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.setStringField("abc");               // setter

Пример для C++ и NDK

    Foo u;                                            // default constructor

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

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

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

Пример на языке Rust

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

    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, возвращает один из этих статусов, представляющий статус метода. В Rust-серверной части используются те же коды исключений, что и в NDK, но перед тем, как передать их пользователю, они преобразуются в собственные ошибки Rust (StatusCode, ExceptionCode). Для ошибок, связанных с сервисом, возвращаемый объект Status или ScopedAStatus использует EX_SERVICE_SPECIFIC вместе с ошибкой, определенной пользователем.

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

Серверная ВМ Определение
Java android/os/Parcel.java
Цена за вызов (СРР) binder/Status.h
NDK android/binder_status.h
Ржавчина android/binder_status.h

Использование разных серверных частей

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

Типы импорта

Независимо от того, является ли определенный тип интерфейсом, parcelable или union, вы можете импортировать его в 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, и обратным вызовом, если она зарегистрирована клиентом сервиса. Однако в зависимости от контекста реализации интерфейса могут использоваться разные названия. Класс-заглушка считывает команды из драйвера Binder и выполняет реализованные вами методы. Предположим, у вас есть 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.

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

В бэкенде Rust:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

При получении вызова связывания ядра информация о вызывающем абоненте доступна в нескольких API. Идентификатор процесса (PID) – это идентификатор процесса Linux, который отправляет транзакцию. Идентификатор пользователя (UI) относится к идентификатору пользователя Linux. При одностороннем вызове идентификатор вызывающего абонента равен 0. Вне контекста транзакции Binder эти функции возвращают 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 уже есть зарезервированные имена, вы можете безопасно переименовать поля, сохранив совместимость протокола. Чтобы продолжить разработку, вам может потребоваться обновить код, но все уже созданные программы продолжат работать.

Неподходящие названия: