AIDL 后端

AIDL 后端是桩代码生成的目标。使用 AIDL 文件时,您始终在特定语言中使用它们以及特定的运行时。根据上下文,您应该使用不同的 AIDL 后端。

在下表中,API 表面的稳定性是指针对此 API 表面编译代码的能力,以便代码可以独立于 system.img libbinder.so 二进制文件交付。

AIDL 具有以下后端

后端 语言 API 表面 构建系统
Java Java SDK/SystemApi(稳定*) 全部
NDK C++ libbinder_ndk(稳定*) aidl_interface
CPP C++ libbinder(不稳定) 全部
Rust Rust libbinder_rs(稳定*) aidl_interface
  • 这些 API 表面是稳定的,但许多 API(例如用于服务管理的 API)保留供平台内部使用,并且不适用于应用。如需详细了解如何在应用中使用 AIDL,请参阅开发者文档
  • Rust 后端是在 Android 12 中引入的;NDK 后端自 Android 10 起可用。
  • Rust crate 构建于 libbinder_ndk 之上,这使其具有稳定性和可移植性。APEX 以与系统端任何其他人相同的方式使用 binder crate。Rust 部分捆绑到 APEX 中并在其中发布。它依赖于系统分区上的 libbinder_ndk.so

构建系统

根据后端,有两种方法可以将 AIDL 编译为桩代码。如需详细了解构建系统,请参阅Soong 模块参考

核心构建系统

在任何 cc_java_ Android.bp 模块(或其 Android.mk 等效模块)中,可以将 .aidl 文件指定为源文件。在这种情况下,将使用 AIDL 的 Java/CPP 后端(而非 NDK 后端),并且使用相应 AIDL 文件所需的类会自动添加到模块中。诸如 local_include_dirs 等选项(用于告知构建系统该模块中 AIDL 文件的根路径)可以在这些模块的 aidl: 组下指定。请注意,Rust 后端仅适用于 Rust。rust_ 模块的处理方式有所不同,因为 AIDL 文件不会指定为源文件。相反,aidl_interface 模块会生成一个名为 <aidl_interface name>-rustrustlib,可以链接到该库。有关更多详细信息,请参阅 Rust AIDL 示例

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 In: &str
Out: String
android.os.Parcelable android::Parcelable N/A N/A
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> In: &[T]
Out: Vec<T>
byte[] std::vector<uint8_t> std::vector<int8_t>1 In: &[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 N/A N/A
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
接口类型 (T) android::sp<T> std::shared_ptr<T>7 binder::Strong
parcelable 类型 (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>,其中 TStringIBinderParcelFileDescriptor 或 parcelable 之一。在 Android 13 或更高版本中,T 可以是任何非原始类型(包括接口类型),但数组除外。AOSP 建议您使用像 T[] 这样的数组类型,因为它们在所有后端都有效。

3. NDK 后端支持 List<T>,其中 TStringParcelFileDescriptor 或 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\> 对象,该对象也在内部进行管理,以防 binder 由另一个进程拥有。以其他方式创建对象会导致双重所有权。

8. 另请参阅 Java/AIDL 类型 byte[]

方向性(in、out 和 inout)

在指定函数参数的类型时,您可以将它们指定为 inoutinout。这控制了 IPC 调用传递方向信息。in 参数说明符表示数据从调用方传递到被调用方。out 参数说明符表示数据从被调用方传递到调用方。in 说明符是默认方向,但是,如果数据类型也可以是 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);
}

UTF8/UTF16

使用 CPP 后端,您可以选择字符串是 utf-8 还是 utf-16。在 AIDL 中将字符串声明为 @utf8InCpp String,以自动将其转换为 utf-8。NDK 和 Rust 后端始终使用 utf-8 字符串。有关 utf8InCpp 注释的更多信息,请参阅 AIDL 中的注释

可为空性

您可以使用 @nullable 注释可以为空的类型。有关 nullable 注释的更多信息,请参阅 AIDL 中的注释

自定义 parcelable

自定义 parcelable 是在目标后端手动实现的 parcelable。仅当您尝试为无法更改的现有自定义 parcelable 添加对其他语言的支持时,才使用自定义 parcelable。

为了声明自定义 parcelable 以便 AIDL 了解它,AIDL parcelable 声明如下所示

    package my.pack.age;
    parcelable Foo;

默认情况下,这声明了一个 Java parcelable,其中 my.pack.age.Foo 是实现 Parcelable 接口的 Java 类。

对于 AIDL 中自定义 CPP 后端 parcelable 的声明,请使用 cpp_header

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

my/pack/age/Foo.h 中的 C++ 实现如下所示

    #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 中自定义 NDK parcelable 的声明,请使用 ndk_header

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

android/pack/age/Foo.h 中的 NDK 实现如下所示

    #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 parcelable 的声明,请使用 rust_type

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

rust_crate/src/lib.rs 中的 Rust 实现如下所示

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 后端自定义 parcelable 提供 <== 运算符,以便在 union 中使用它们。

默认值

结构化 parcelable 可以为原始类型、String 和这些类型的数组声明每个字段的默认值。

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

在 Java 后端,当缺少默认值时,原始类型的字段初始化为零值,非原始类型的字段初始化为 null

在其他后端,当未定义默认值时,字段将使用默认初始化的值进行初始化。例如,在 C++ 后端,String 字段初始化为空字符串,List<T> 字段初始化为空 vector<T>@nullable 字段初始化为 null 值字段。

联合

AIDL 联合是标记的,并且其功能在所有后端中都相似。它们默认构造为第一个字段的默认值,并且它们具有特定于语言的方式与之交互。

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

Java 示例

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

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

    u.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 示例

在 Rust 中,联合实现为枚举,并且没有显式的 getter 和 setter。

    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 操作系统提供了内置的错误类型,供服务在报告错误时使用。这些类型由 binder 使用,并且可以由任何实现 binder 接口的服务使用。它们的使用在 AIDL 定义中得到了充分的文档记录,并且它们不需要任何用户定义的状态或返回类型。

带有错误的输出参数

当 AIDL 函数报告错误时,该函数可能不会初始化或修改输出参数。具体而言,如果在解编组期间发生错误(而不是在事务处理本身期间发生错误),则可能会修改输出参数。通常,当从 AIDL 函数获取错误时,所有 inoutout 参数以及返回值(在某些后端中充当 out 参数)都应被视为处于不确定状态。

要使用的错误值

许多内置错误值可以在任何 AIDL 接口中使用,但有些错误值以特殊方式处理。例如,EX_UNSUPPORTED_OPERATIONEX_ILLEGAL_ARGUMENT 在描述错误情况时可以使用,但 EX_TRANSACTION_FAILED 不得使用,因为它会被底层基础架构特殊处理。查看后端特定的定义,以获取有关这些内置值的更多信息。

如果 AIDL 接口需要内置错误类型未涵盖的其他错误值,则它们可以使用特殊的特定于服务的内置错误,该错误允许包含用户定义的特定于服务的错误值。这些特定于服务的错误通常在 AIDL 接口中定义为 const intint 支持的 enum,并且不会被 binder 解析。

在 Java 中,错误映射到异常,例如 android.os.RemoteException。对于特定于服务的异常,Java 使用 android.os.ServiceSpecificException 以及用户定义的错误。

Android 中的本机代码不使用异常。CPP 后端使用 android::binder::Status。NDK 后端使用 ndk::ScopedAStatus。AIDL 生成的每个方法都返回其中之一,表示方法的状​​态。Rust 后端使用与 NDK 相同的异常代码值,但在将它们传递给用户之前,将其转换为本机 Rust 错误(StatusCodeExceptionCode)。对于特定于服务的错误,返回的 StatusScopedAStatus 使用 EX_SERVICE_SPECIFIC 以及用户定义的错误。

内置错误类型可以在以下文件中找到

后端 定义
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Rust android/binder_status.h

使用各种后端

这些说明特定于 Android 平台代码。这些示例使用定义的类型 my.package.IFoo。有关如何使用 Rust 后端的说明,请参阅 Android Rust 模式页面上的 Rust AIDL 示例

导入类型

无论定义的类型是接口、parcelable 还是联合,您都可以在 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 后端中,您必须包含其根类型的标头。例如,当导入在 my/package/IFoo.aidl 中定义的嵌套类型 BarIFoo 是文件的根类型)时,您必须为 CPP 后端包含 <my/package/IFoo.h>(或为 NDK 后端包含 <aidl/my/package/IFoo.h>)。

实现接口

要实现接口,您必须从本机桩类继承。接口的实现通常在向服务管理器或 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 will run on this thread.
    std::future::pending().await
}

与其他选项的一个重要区别是,在使用异步 Rust 和单线程运行时时,我们调用 join_thread_pool。这是因为您需要为 Tokio 提供一个线程,以便它可以执行生成的任务。在此示例中,主线程将用于此目的。使用 tokio::spawn 生成的任何任务都将在主线程上执行。

在异步 Rust 后端中,使用多线程运行时

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

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

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

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

使用多线程 Tokio 运行时,生成的任务不会在主线程上执行。因此,在主线程上调用 join_thread_pool 以便主线程不仅仅是空闲更有意义。您必须将调用包装在 block_in_place 中以离开异步上下文。

您可以请求在托管 binder 的服务终止时收到通知。这可以帮助避免泄漏回调代理或协助错误恢复。在 binder 代理对象上进行这些调用。

  • 在 Java 中,使用 android.os.IBinder::linkToDeath
  • 在 CPP 后端中,使用 android::IBinder::linkToDeath
  • 在 NDK 后端中,使用 AIBinder_linkToDeath
  • 在 Rust 后端中,创建一个 DeathRecipient 对象,然后调用 my_binder.link_to_death(&mut my_death_recipient)。请注意,由于 DeathRecipient 拥有回调,因此您必须保持该对象处于活动状态,只要您想接收通知。

调用方信息

当接收到内核 binder 调用时,调用方信息在多个 API 中可用。PID(或进程 ID)是指发送事务处理的进程的 Linux 进程 ID。UID(或用户 ID)是指 Linux 用户 ID。当接收到单向调用时,调用 PID 为 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 方法,就像您正在实现 AIDL 文件中定义的任何其他 IPC 方法一样。执行此操作时,您应将转储限制为应用权限 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<()>

使用弱指针

您可以持有对 binder 对象的弱引用。

虽然 Java 支持 WeakReference,但它不支持本机层的弱 binder 引用。

在 CPP 后端中,弱类型为 wp<IFoo>

在 NDK 后端中,使用 ScopedAIBinder_Weak

#include <android/binder_auto_utils.h>

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

在 Rust 后端中,您可以使用 WpIBinderWeak<IFoo>

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

动态获取接口描述符

接口描述符标识接口的类型。这在调试或当您有未知的 binder 时非常有用。

在 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 中不支持此功能。

对于在 AIDL 中定义的枚举 MyEnum,迭代提供如下。

在 CPP 后端中

    ::android::enum_range<MyEnum>()

在 NDK 后端中

   ::ndk::enum_range<MyEnum>()

在 Rust 后端中

    MyEnum::enum_values()

线程管理

进程中 libbinder 的每个实例都维护一个线程池。对于大多数用例,这应该只有一个线程池,在所有后端之间共享。唯一的例外是当供应商代码可能加载另一个 libbinder 副本以与 /dev/vndbinder 通信时。由于它位于单独的 binder 节点上,因此线程池不共享。

对于 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 定义中已经有保留名称,您可以安全地重命名字段,同时保持协议兼容性;您可能需要更新您的代码以继续构建,但任何已构建的程序都将继续互操作。

要避免的名称