AIDL 后端是生成桩代码的目标。在使用 AIDL 文件时,您始终是采用特定语言在特定的运行时环境中使用这些文件。因此,您应该根据具体情况使用不同的 AIDL 后端。
在下表中,API Surface 稳定是指以下能力:可针对此 API Surface 编译代码,且相应代码可以独立于 system.img
libbinder.so
二进制文件提供。
AIDL 的后端如下所示:
后端 | 语言 | API Surface | 构建系统 |
---|---|---|---|
Java | Java | SDK/SystemApi(稳定*) | 全部 |
NDK | C++ | libbinder_ndk(稳定*) | aidl_interface |
每次来电费用 | C++ | libbinder(不稳定) | 全部 |
Rust | Rust | libbinder_rs(稳定*) | aidl_interface |
- 这些 API Surface 是稳定的,但其中许多 API(例如用于服务管理的 API)已预留给内部平台使用,应用无法使用。如需详细了解如何在应用中使用 AIDL,请参阅开发者文档。
- Rust 后端是在 Android 12 中引入的;NDK 后端从 Android 10 开始一直可用。
- Rust crate 是基于
libbinder_ndk
构建的,这使其具备良好的稳定性和可移植性。APEX 使用 binder crate 的方式与系统端使用其他可编译单元的方式相同。Rust 部分会捆绑到 APEX 中,并在其内提供。这具体取决于 system 分区上的libbinder_ndk.so
。
构建系统
根据不同的后端,可以采用两种方式将 AIDL 编译成桩代码。如需详细了解构建系统,请参阅 Soong 模块参考文档。
核心构建系统
在任何 cc_
或 java_
Android.bp 模块(或等效的 Android.mk
模块)中,可以将 .aidl
文件指定为源文件。在这种情况下使用的是 AIDL 的 Java/CPP 后端(而不是 NDK 后端),并且使用相应 AIDL 文件的类会自动添加到相应模块中。您可以在这些模块中的 aidl:
组下指定选项(例如 local_include_dirs
,用于告知构建系统该模块中 AIDL 文件的根路径)。请注意,Rust 后端仅可与 Rust 搭配使用。在未被指定为源文件的 AIDL 文件中,系统会以不同的方式处理 rust_
模块。aidl_interface
模块会生成一个名为 <aidl_interface name>-rust
的 rustlib
,您可以链接它。如需了解详情,请参阅 Rust AIDL 示例。
aidl_interface
此构建系统使用的类型必须是结构化类型。为了实现结构化,Parcelable 必须直接包含字段,而不是直接以目标语言定义的类型的声明。如需了解结构化 AIDL 如何与稳定的 AIDL 配合使用,请参阅结构化 AIDL 与稳定的 AIDL。
类型
您可以将 aidl
编译器视为类型的参考实现。
在创建接口时,请调用 aidl --lang=<backend> ...
来查看生成的接口文件。在使用 aidl_interface
模块时,您可以在 out/soong/.intermediates/<path to module>/
中查看输出。
Java/AIDL 类型 | C++ 类型 | NDK 类型 | Rust 类型 |
---|---|---|---|
布尔值 | bool | bool | bool |
byte | 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 |
字符串 | android::String16 | std::string | In: &str Out: String |
android.os.Parcelable | android::Parcelable | 不适用 | 不适用 |
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 | 不适用 | 不适用 |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
interface 类型 (T) | android::sp<T> | std::shared_ptr<T>7 | binder::Strong |
parcelable 类型 (T) | T | T | T |
union 类型 (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
可以是除数组以外的任何非基元类型(包括 interface 类型)。AOSP 建议您使用类似 T[]
的数组类型,因为这些数组类型适用于所有后端。
3. NDK 后端支持 List<T>
,其中 T
为 String
、ParcelFileDescriptor
或 Parcelable 中的一种。在 Android 13 或更高版本中,T
可以是除数组以外的任何非基元类型。
4. Rust 代码的类型传递方式有所不同,具体取决于类型是输入(参数)还是输出(返回值)。
5. Android 12 及更高版本支持 union 类型。
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
表示数据从被调用方传递给调用方。inout
是这两种方向的组合。不过,Android 团队建议您避免使用参数说明符 inout
。如果您将 inout
用于具有版本化接口和旧版被调用方,那么仅在调用方中出现的额外字段会重置其默认值。对于 Rust,常规 inout
类型会收到 &mut Vec<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 编写的注解。
是否可为 null
您可以使用 @nullable
对可为 null 的类型进行注解。如需详细了解 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);
然后,您就可以在 AIDL 文件中将此 parcelable 用作类型,但 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 |
每次来电费用 | binder/Status.h |
NDK | android/binder_status.h |
Rust | android/binder_status.h |
使用各种后端
以下说明专用于 Android 平台代码。这些示例使用的是已定义的类型 my.package.IFoo
。如需了解如何使用 Rust 后端,请参阅 Android Rust 模式页面上的 Rust AIDL 示例。
导入类型
无论定义的类型是 interface、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>
)。
实现服务
若要实现服务,必须从原生桩类继承。该类从 Binder 驱动程序读取命令并执行您实现的方法。假设您的 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(())
}
}
或者,使用异步 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();
服务的 bug 报告和调试 API
bug 报告在运行(例如,使用 adb bugreport
运行)时会从整个系统中收集信息,以帮助调试各种问题。对于 AIDL 服务,bug 报告在通过服务管理器注册的所有服务中都使用二进制文件 dumpsys
,以将其信息转储到 bug 报告中。您还可以在命令行中使用 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 位于单独的 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 定义中使用了预留名称,则可以安全地重命名字段,同时保持协议兼容;您可能需要更新代码才能继续构建,但任何已构建的程序都将继续互操作。
应避免使用的名称: