AIDL 后端

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

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

AIDL 有以下后端:

后端API 表面构建系统
爪哇爪哇SDK/SystemApi (稳定*)全部
NDK C++ libbinder_ndk(稳定*) aidl_interface
CPP C++ libbinder(不稳定)全部
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>-rust rustlib rustlib,它可以被链接。有关更多详细信息,请参阅Rust AIDL 示例

aidl_interface

请参阅稳定的 AIDL 。与此构建系统一起使用的类型必须是结构化的;即直接用 AIDL 表示。这意味着不能使用自定义 Parcelables。

类型

您可以将aidl编译器视为类型的参考实现。创建接口时,调用aidl --lang=<backend> ...查看生成的接口文件。当您使用aidl_interface模块时,您可以在out/soong/.intermediates/<path to module>/中查看输出。

Java/AIDL 类型C++ 类型NDK 类型锈型
布尔值布尔布尔布尔
字节int8_t int8_t i8
字符char16_t char16_t u16
整数int32_t int32_t i32
int64_t int64_t i64
漂浮漂浮漂浮f32
双倍的双倍的双倍的f64
细绳安卓::String16标准::字符串细绳
android.os.Parcelable android::Parcelable不适用不适用
绑定器安卓::IBinder ndk::SpAIBinder活页夹::SpIBinder
T[]标准::向量<T>标准::向量<T>在:&[T]
输出: Vec<T>
字节[] std::vector<uint8_t> std::vector<int8_t> 1在:&[u8]
输出:Vec<u8>
列表<T>标准::向量<T> 2标准::向量<T> 3在:&[T] 4
输出: Vec<T>
文件描述符android::base::unique_fd不适用binder::parcel::ParcelFileDescriptor
包裹文件描述符android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
接口类型 (T)安卓::sp<T> std::shared_ptr<T>活页夹::强
可包裹类型 (T)
联合类型 (T) 5
T[N] 6标准::数组<T, N>标准::数组<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 后端,固定大小的数组表示为数组类型。

方向性(输入/输出/输入)

指定函数参数的类型时,可以将它们指定为inoutinout 。这控制了为 IPC 调用传递方向信息的方向。 in是默认方向,表示数据从调用者传递到被调用者。 out表示数据从被调用者传递给调用者。 inout是这两者的结合。但是,Android 团队建议您避免使用参数说明符inout 。如果您将inout与版本化接口和较旧的被调用者一起使用,则仅存在于调用者中的附加字段将重置为其默认值。对于 Rust,普通的inout类型接收&mut Vec<T> ,而 list inout类型接收&mut Vec<T>

UTF8/UTF16

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

可空性

您可以使用@nullable注释 Java 后端中可以为空的类型,以将空值公开给 CPP 和 NDK 后端。在 Rust 后端,这些@nullable类型公开为Option<T> 。默认情况下,本机服务器拒绝空值。唯一的例外是interfaceIBinder类型,对于 NDK 读取和 CPP/NDK 写入,它们始终可以为空。有关nullable注释的更多信息,请参阅AIDL 中的注释

自定义 Parcelables

在核心构建系统的 C++ 和 Java 后端,您可以声明在目标后端(C++ 或 Java)中手动实现的 Parcelable。

    package my.package;
    parcelable Foo;

或使用 C++ 标头声明:

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

然后你可以在 AIDL 文件中使用这个 parcelable 作为类型,但它不会由 AIDL 生成。

Rust 不支持自定义 parcelables。

默认值

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

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

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

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

错误处理

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以及用户定义的错误。

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

后端定义
爪哇android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
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> (或<aidl/my/package/IFoo.h>用于 NDK 后端)。

实施服务

要实现服务,您必须从本机存根类继承。此类从活页夹驱动程序读取命令并执行您实现的方法。想象一下,你有一个这样的 AIDL 文件:

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

在 Java 中,您必须从此类扩展:

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

在 CPP 后端:

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

在 NDK 后端(注意额外的aidl命名空间):

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

在 Rust 后端:

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

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

    impl Interface for MyFoo {}

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

注册和获取服务

Android 平台中的服务通常注册到servicemanager进程中。除了下面的 API 之外,一些 API 还会检查服务(这意味着如果服务不可用,它们会立即返回)。检查相应的servicemanager接口以获取确切的详细信息。这些操作只能在针对 Android 平台编译时进行。

在 Java 中:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // getting
    myService = IFoo.Stub.asInterface(ServiceManager.getService("service-name"));
    // waiting until service comes up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));

在 CPP 后端:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // getting
    status_t err = getService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

在 NDK 后端(注意额外的aidl命名空间):

    #include <android/binder_manager.h>
    // registering
    status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // getting
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_waitForService("service-name")));

在 Rust 后端:

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

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

您可以请求获取有关托管活页夹的服务何时终止的通知。这可以帮助避免泄漏回调代理或帮助错误恢复。在活页夹代理对象上进行这些调用。

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

来电者信息

接收内核绑定器调用时,调用者信息可在多个 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<()>

动态获取接口描述符

接口描述符标识接口的类型。这在调试或有未知绑定器时很有用。

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

线程管理

进程中的每个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();

保留名称

C++、Java 和 Rust 保留一些名称作为关键字或用于特定语言的用途。虽然 AIDL 不会根据语言规则强制实施限制,但使用与保留名称匹配的字段或类型名称可能会导致 C++ 或 Java 编译失败。对于 Rust,字段或类型使用“原始标识符”语法重命名,可使用r#前缀访问。

我们建议您尽可能避免在 AIDL 定义中使用保留名称,以避免不符合人体工程学的绑定或彻底的编译失败。

如果您的 AIDL 定义中已经有保留名称,您可以安全地重命名字段,同时保持协议兼容;您可能需要更新代码才能继续构建,但任何已构建的程序都将继续互操作。

要避免的名称:* C++ 关键字* Java 关键字* Rust 关键字