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>-rust
的 rustlib
,可以链接到该库。有关更多详细信息,请参阅 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>
,其中 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\>
对象,该对象也在内部进行管理,以防 binder 由另一个进程拥有。以其他方式创建对象会导致双重所有权。
8. 另请参阅 Java/AIDL 类型 byte[]
。
方向性(in、out 和 inout)
在指定函数参数的类型时,您可以将它们指定为 in
、out
或 inout
。这控制了 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 函数获取错误时,所有 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
以及用户定义的错误。
内置错误类型可以在以下文件中找到
后端 | 定义 |
---|---|
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
中定义的嵌套类型 Bar
(IFoo
是文件的根类型)时,您必须为 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 后端中,您可以使用 WpIBinder
或 Weak<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 定义中已经有保留名称,您可以安全地重命名字段,同时保持协议兼容性;您可能需要更新您的代码以继续构建,但任何已构建的程序都将继续互操作。
要避免的名称