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 |
细绳 | 机器人::字符串16 | 标准::字符串 | 细绳 |
android.os.Parcelable | android::Parcelable | 不适用 | 不适用 |
活页夹 | android::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 | 不适用 | 活页夹::包裹::包裹文件描述符 |
包裹文件描述符 | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | 活页夹::包裹::包裹文件描述符 |
接口类型 (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>
,其中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 后端中,固定大小的数组表示为数组类型。
方向性(输入/输出/输入)
指定函数参数的类型时,您可以将它们指定为in
、 out
或inout
。这控制为 IPC 调用传递方向信息的方向。 in
是默认方向,它表示数据从调用者传递到被调用者。 out
表示数据从被调用方传递给调用方。 inout
是这两者的组合。但是,Android 团队建议您避免使用参数说明符inout
。如果您将inout
与版本化接口和较旧的被调用方一起使用,则仅存在于调用方中的其他字段将重置为其默认值。对于 Rust,普通inout
类型接收&mut Vec<T>
,而列表inout
类型接收&mut Vec<T>
。
UTF8/UTF16
使用 CPP 后端,您可以选择字符串是 utf-8 还是 utf-16。在 AIDL 中将字符串声明为@utf8InCpp String
以自动将它们转换为 utf-8。 NDK 和 Rust 后端始终使用 utf-8 字符串。有关utf8InCpp
注释的更多信息,请参阅AIDL 中的注释。
可空性
您可以使用@nullable
注释 Java 后端中可以为 null 的类型,以将 null 值公开给 CPP 和 NDK 后端。在 Rust 后端,这些@nullable
类型被公开为Option<T>
。本机服务器默认拒绝空值。唯一的例外是interface
和IBinder
类型,它们对于 NDK 读取和 CPP/NDK 写入始终可以为 null。有关可为nullable
的注释的更多信息,请参阅AIDL 中的注释。
自定义包裹
在核心构建系统的 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。
默认值
结构化 parcelables 可以为基元、 String
和这些类型的数组声明每个字段的默认值。
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
在 Java 后端,当缺少默认值时,字段被初始化为原始类型的零值和非原始类型的null
。
在其他后端中,当未定义默认值时,字段将使用默认初始化值进行初始化。例如,在 C++ 后端中, String
字段被初始化为空字符串, List<T>
字段被初始化为空vector<T>
。 @nullable
字段被初始化为空值字段。
错误处理
Android 操作系统为报告错误时使用的服务提供了内置错误类型。这些由活页夹使用,并且可以由任何实现活页夹接口的服务使用。它们的使用在 AIDL 定义中有详细记录,并且不需要任何用户定义的状态或返回类型。
错误的输出参数
当 AIDL 函数报告错误时,该函数可能不会初始化或修改输出参数。具体来说,如果错误发生在拆包期间而不是发生在交易本身的处理期间,则可以修改输出参数。通常,当从 AIDL 函数中获取错误时,所有inout
和out
参数以及返回值(在某些后端中充当out
参数)应被视为处于不确定状态。
使用哪些错误值
许多内置错误值可以在任何 AIDL 接口中使用,但有些会以特殊方式处理。例如, EX_UNSUPPORTED_OPERATION
和EX_ILLEGAL_ARGUMENT
在描述错误情况时可以使用,但不能使用EX_TRANSACTION_FAILED
,因为它被底层基础设施特殊对待。检查后端特定定义以获取有关这些内置值的更多信息。
如果 AIDL 接口需要内置错误类型未涵盖的其他错误值,则它们可以使用特殊的特定于服务的内置错误,允许包含用户定义的特定于服务的错误值.这些特定于服务的错误通常在 AIDL 接口中定义为const int
或int
支持的enum
,并且不会被 binder 解析。
在 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 |
NDK | android/binder_status.h |
锈 | android/binder_status.h |
使用各种后端
这些说明特定于 Android 平台代码。这些示例使用定义的类型my.package.IFoo
。有关如何使用 Rust 后端的说明,请参阅Android Rust 模式页面上的Rust AIDL 示例。
导入类型
无论定义的类型是接口、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 后端中,您必须包含其根类型的标头。例如,当导入在my/package/IFoo.aidl
( IFoo
是文件的根类型)中定义的嵌套类型Bar
时,您必须包含<my/package/IFoo.h>
作为 CPP 后端(或<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 平台进行编译时才能完成。
在爪哇中:
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
status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(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(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。当在活页夹事务上下文之外时,这些函数返回当前进程的 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 服务,bugreports 在服务管理器注册的所有服务上使用二进制dumpsys
将它们的信息转储到 bugreport 中。您还可以在命令行上使用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 关键字