在 Android 上使用 Rust 与 Binder:Birthday Service 完整实战教程
【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust
导读:本文以 comprehensive-rust 课程(Google Android 团队官方 Rust 课程)中的 Birthday Service 教程为核心,完整演示如何在 Android 平台上用 Rust 定义 AIDL 接口、用 Rust 实现 Binder 服务端与客户端,并完成构建、推送、注册、调用与排障的端到端闭环。读完本文,你将掌握 AIDL 接口声明规范、Rust 端 Binder 生成的 trait 与
Bn*包装类型、add_service/join_thread_pool的服务注册模型,以及service call等设备侧调试手段。
教程背景:为什么在 Android 上用 Rust 写 Binder 服务
Android 系统组件之间的进程间通信(IPC)主要基于 Binder 机制。传统上,开发者在 C++ 或 Java 中使用 AIDL(Android Interface Definition Language)声明接口,再由aidl编译器生成跨语言绑定代码。在 Google 的 Rust 课程 src/android/aidl.md 中明确指出,Rust 在 Android 的 Binder 生态中是一等公民,具备两大能力:
- Rust 代码可以调用现有的 AIDL 服务器(作为客户端);
- 可以在 Rust 中创建全新的 AIDL 服务器(作为服务端),并且设备上的其他进程可以直接调用这个 Rust 服务。
Birthday Service 教程正是为演示"用 Rust 与 Binder 打交道"而设计的完整示例:先创建一个 Binder 接口,然后实现服务端,再编写一个与之通信的客户端。整个示例的代码全部位于仓库的 src/android/aidl/birthday_service/ 目录下,配套讲解文档位于 src/android/aidl/example-service/ 目录。
第一步:用 AIDL 声明服务接口
服务端与客户端共享的 API 契约通过 AIDL 文件声明。教程中的接口文件位于 src/android/aidl/birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:
package com.example.birthdayservice; /** Birthday service interface. */ interface IBirthdayService { /** Generate a Happy Birthday message. */ String wishHappyBirthday(String name, int years); }这段接口声明了唯一的 IPC 方法wishHappyBirthday:调用方传入名字与年龄,服务端返回拼装好的祝福字符串。完整版接口文件还额外声明了wishWithInfo、wishWithProvider、wishWithErasedProvider、wishFromFile等演示"更复杂 Binder 类型"的方法,后文会结合源码逐一展开。
AIDL 包名与目录结构的强约束
讲解文档 src/android/aidl/example-service/interface.md 特别强调:aidl/目录下的目录结构必须与 AIDL 文件中的包名完全一致。例如包名是com.example.birthdayservice,则文件必须放在:
aidl/com/example/birthdayservice/IBirthdayService.aidl这一约束是 AIDL 编译器(Soong 的aidl_interface模块)解析包名与生成 Rust crate 路径的前提。
在 Soong 构建系统中启用 Rust 后端
AIDL 接口需要通过 Soong 构建模块声明,配置文件为 src/android/aidl/birthday_service/aidl/Android.bp:
aidl_interface { name: "com.example.birthdayservice", srcs: ["com/example/birthdayservice/*.aidl"], unstable: true, backend: { rust: { // Rust is not enabled by default enabled: true, }, }, }要点解读:
name是该 AIDL 接口模块的名称,构建系统会据此生成对应的 Rust cratecom.example.birthdayservice-rust;srcs使用通配符收集com/example/birthdayservice/下的所有.aidl文件;unstable: true:教学示例通过它绕开对已发布(frozen)AIDL 接口的版本限制,实际产品代码应遵循严格的接口版本管理流程;backend.rust.enabled: true:Rust 后端默认并不启用,必须显式打开才会生成 Rust 绑定代码。这正是"Rust 不是 AIDL 默认后端"这一事实在构建配置层面的直接体现。
第二步:查看 Binder 为接口生成的 Rust API
AIDL 编译器会为每个接口定义生成一个 Rust trait。讲解文档 src/android/aidl/example-service/service-bindings.md 给出了经过清理和简化的生成代码形态(真实代码生成在构建输出目录out/soong/.intermediates/.../com_example_birthdayservice.rs):
trait IBirthdayService { fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String>; }关键信息:
- 生成的 trait 名与 AIDL 接口名一致,客户端与服务端使用的是同一个 trait:服务端实现它,客户端通过它发起调用;
- 每个 IPC 方法都接收
&self(共享引用)而不是&mut self,原因是 Binder 在线程池上并发处理多个请求,服务方法只能拿到self的共享引用; - 返回类型统一包裹为
binder::Result<T>,IPC 错误(如服务未注册、类型不匹配)通过 Rust 的Result显式传播; - 注意 AIDL 的
String作为入参时映射为 Rust 的&str,而作为返回值时映射为String,同样的 AIDL 类型在不同位置会生成不同的 Rust 类型。
课程文档还特别指出(见 src/android/aidl/example-service/interface.md):生成 trait 的完整模块路径为com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService,路径中的每一段——crate 名、aidl、包名、接口名——都与 AIDL 包名和目录结构一一对应。
第三步:用 Rust 实现 AIDL 服务端
服务实现体(lib.rs)
服务逻辑实现在 Rust 库 crate 中,源码位于 src/android/aidl/birthday_service/src/lib.rs:
//! Implementation of the `IBirthdayService` AIDL interface. use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService; use com_example_birthdayservice::binder; /// The `IBirthdayService` implementation. pub struct BirthdayService; impl binder::Interface for BirthdayService {} impl IBirthdayService for BirthdayService { fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String> { Ok(format!("Happy Birthday {name}, congratulations with the {years} years!")) } }实现要点:
- 必须同时实现两个 trait:
binder::Interface(Binder 框架要求的基础接口)和生成的IBirthdayService(业务接口); BirthdayService是一个零字段的结构体(unit struct),因为当前方法无需内部状态;- 方法体用
format!拼接祝福消息并通过Ok(...)返回,错误类型直接沿用binder::Result。
状态管理:为什么方法接收 &self 而不是 &mut self
课程文档在 src/android/aidl/example-service/implementation.md 中强调了一个重要设计约束:Binder 在线程池上响应请求,可能并行处理多个 IPC 调用,因此服务方法只能获得self的共享引用。如果服务需要维护可变状态,就必须把状态放入Mutex等同步原语中以保证安全修改:
- 例如在
BirthdayService中增加Mutex<Counter>之类的字段来统计调用次数; - 具体采用哪种并发方案(
Mutex、RwLock或原子类型)取决于服务的状态访问模式,这是服务端设计时需要根据业务自行权衡的部分。
服务端二进制(server.rs)
仅有实现体还不够,还需要一个可执行入口把服务注册进 Binder 并启动监听。源码位于 src/android/aidl/birthday_service/src/server.rs:
//! Birthday service. use birthdayservice::BirthdayService; use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::BnBirthdayService; use com_example_birthdayservice::binder; const SERVICE_IDENTIFIER: &str = "birthdayservice"; /// Entry point for birthday service. fn main() { let birthday_service = BirthdayService; let birthday_service_binder = BnBirthdayService::new_binder( birthday_service, binder::BinderFeatures::default(), ); binder::add_service(SERVICE_IDENTIFIER, birthday_service_binder.as_binder()) .expect("Failed to register service"); binder::ProcessState::join_thread_pool(); }课程文档 src/android/aidl/example-service/server.md 把"把用户自定义服务变成 Binder 服务"拆解为四个步骤,并强调这与 C++ 等其他语言的用法相比可能更显繁琐,需要理解每一步的动机:
- 创建服务实例:
let birthday_service = BirthdayService;; - 把服务对象包装进生成的
Bn*类型:BnBirthdayService::new_binder(birthday_service, BinderFeatures::default())。这个BnBirthdayService由 Binder 编译器生成,提供通用的 Binder 功能,类似于 C++ 中的BnBinder基类。由于Rust 没有继承机制,这里用组合(composition)替代继承:把BirthdayService塞进生成的BnBirthdayService中; - 调用
binder::add_service:传入服务标识符(这里为字符串常量"birthdayservice")和服务对象(即包装后的BnBirthdayService),完成向系统服务管理器的注册,注册失败时.expect("Failed to register service")直接终止进程; - 调用
binder::ProcessState::join_thread_pool():让当前线程加入 Binder 的线程池并开始监听连接,此调用通常不会返回。
服务端 Soong 配置
服务端二进制的构建配置位于 src/android/aidl/birthday_service/Android.bp:
rust_library { name: "libbirthdayservice", crate_name: "birthdayservice", srcs: ["src/lib.rs"], rustlibs: [ "com.example.birthdayservice-rust", ], } rust_binary { name: "birthday_server", crate_name: "birthday_server", srcs: ["src/server.rs"], rustlibs: [ "com.example.birthdayservice-rust", "libbirthdayservice", ], prefer_rlib: true, // To avoid dynamic link error. }要点:
- 服务实现被拆分为
rust_library(libbirthdayservice),与rust_binary(birthday_server)分离,职责清晰; - 两者都依赖 AIDL 生成的 Rust crate
com.example.birthdayservice-rust; prefer_rlib: true表示优先使用静态链接的 rlib,避免设备上出现动态链接错误。
第四步:部署服务到设备
课程文档 src/android/aidl/example-service/deploy.md 给出了完整部署流程,对应脚本片段位于 src/android/build_all.sh。
构建并推送服务端,然后在设备上启动:
m birthday_server adb push "$ANDROID_PRODUCT_OUT/system/bin/birthday_server" /data/local/tmp adb shell /data/local/tmp/birthday_server注意:脚本中启动服务时使用
adb shell ... &后台运行,并配合pkill -f birthday_server做进程清理;在另一个终端里继续后续操作。
在另一个终端确认服务已注册:
adb shell service check birthdayservice预期输出:
Service birthdayservice: found如果服务尚未注册,service check会输出not found(构建脚本中通过循环等待服务出现)。
使用service call直接调用服务(不经过自定义客户端,适合快速验证):
adb shell service call birthdayservice 1 s16 Bob i32 24这条命令的语义是:调用birthdayservice服务的第 1 个方法(wishHappyBirthday),参数依次为 UTF-16 字符串Bob(s16)和 32 位整数24(i32)。预期返回的 Parcel 内容(十六进制转储)为:
Result: Parcel( 0x00000000: 00000000 00000036 00610048 00700070 '....6...H.a.p.p.' 0x00000010: 00200079 00690042 00740072 00640068 'y. .B.i.r.t.h.d.' 0x00000020: 00790061 00420020 0062006f 0020002c 'a.y. .B.o.b.,. .' 0x00000030: 006f0063 0067006e 00610072 00750074 'c.o.n.g.r.a.t.u.' 0x00000040: 0061006c 00690074 006e006f 00200073 'l.a.t.i.o.n.s. .' 0x00000050: 00690077 00680074 00740020 00650068 'w.i.t.h. .t.h.e.' 0x00000060: 00320020 00200034 00650079 00720061 ' .2.4. .y.e.a.r.' 0x00000070: 00210073 00000000 's.!..... ')Parcel 转储中的 UTF-16 编码可以还原出完整的服务端消息:"Happy Birthday Bob, congratulations with the 24 years!"——这说明service call绕过了类型化的客户端,直接以原始 Parcel 数据驱动 IPC,是验证服务端是否正常工作的利器。
第五步:编写并运行 Rust 客户端
客户端代码(client.rs)
客户端源码位于 src/android/aidl/birthday_service/src/client.rs:
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService; use com_example_birthdayservice::binder; const SERVICE_IDENTIFIER: &str = "birthdayservice"; /// Call the birthday service. fn main() -> Result<(), Box<dyn Error>> { let name = std::env::args().nth(1).unwrap_or_else(|| String::from("Bob")); let years = std::env::args() .nth(2) .and_then(|arg| arg.parse::<i32>().ok()) .unwrap_or(42); binder::ProcessState::start_thread_pool(); let service = binder::get_interface::<dyn IBirthdayService>(SERVICE_IDENTIFIER) .map_err(|_| "Failed to connect to BirthdayService")?; // Call the service. let msg = service.wishHappyBirthday(&name, years)?; println!("{msg}"); Ok(()) }客户端流程分四步:
- 解析命令行参数:从
argv[1]读取名字(缺省Bob)、从argv[2]读取年龄(缺省42),演示了 Rust 标准库的参数处理惯用法; binder::ProcessState::start_thread_pool():客户端进程也需要启动 Binder 线程池,以接收来自 Binder 驱动的回调;binder::get_interface::<dyn IBirthdayService>(SERVICE_IDENTIFIER):按服务标识符向系统服务管理器查询并建立连接,返回Strong<dyn IBirthdayService>trait 对象;连接失败时通过map_err转成错误信息;- 发起调用:
service.wishHappyBirthday(&name, years)?,?运算符把 IPC 错误向上传播。
Strong:Binder 的自定义智能指针
课程文档在 src/android/aidl/example-service/client.md 中对Strong<dyn IBirthdayService>做了专门说明:
Strong是 Binder 的自定义智能指针类型,同时维护两类引用计数:进程内的引用计数(对应 Rust trait 对象的生命周期)与全局 Binder 引用计数(记录有多少进程持有了这个 Binder 对象的引用);- 客户端与服务端使用的 trait是同一个生成的 trait——对某个 Binder 接口而言,无论客户端还是服务端,Rust 编译器都只生成一个 trait,客户端把它当作"远端对象"的接口,服务端把它当作"本地对象"的实现契约;
- 客户端使用的服务标识符必须与注册时一致。教程还给出工程化建议:这个标识符最好定义在客户端和服务端都能依赖的公共 crate 中,避免字符串常量在不同模块间漂移失配。
客户端 Soong 配置
客户端的构建配置同样位于 src/android/aidl/birthday_service/Android.bp:
rust_binary { name: "birthday_client", crate_name: "birthday_client", srcs: ["src/client.rs"], rustlibs: [ "com.example.birthdayservice-rust", ], prefer_rlib: true, // To avoid dynamic link error. }一个值得注意的细节(课程文档在 src/android/aidl/example-service/client.md 中明确指出):客户端并不依赖libbirthdayservice,只依赖生成的com.example.birthdayservice-rust。这印证了接口与实现彻底分离的架构——客户端只需要接口契约(生成的 trait),根本不需要接触服务端实现代码。
构建、推送并运行客户端
m birthday_client adb push "$ANDROID_PRODUCT_OUT/system/bin/birthday_client" /data/local/tmp adb shell /data/local/tmp/birthday_client Charlie 60预期输出:
Happy Birthday Charlie, congratulations with the 60 years!至此,"AIDL 接口声明 → 服务端实现 → 服务注册 → 客户端调用"的完整链路跑通。
进阶一:修改接口定义与同步更新两端
教程没有止步于单接口示例,而是演示了 API 演化的完整流程(src/android/aidl/example-service/changing-definition.md 与 changing-implementation.md)。
修改 AIDL 接口:让客户端可以传入多行祝福卡片文字:
package com.example.birthdayservice; /** Birthday service interface. */ interface IBirthdayService { /** Generate a Happy Birthday message. */ String wishHappyBirthday(String name, int years, in String[] text); }AIDL 类型到 Rust 类型的映射规则:编译器重新生成 trait 后,in String[]映射为 Rust 的切片&[String]:
trait IBirthdayService { fn wishHappyBirthday( &self, name: &str, years: i32, text: &[String], ) -> binder::Result<String>; }课程文档总结了in/out/inout数组参数与返回值在 Rust 绑定中的通用映射规律:
in数组参数 → Rust 切片(&[T]),只读借用;out与inout参数 →&mut Vec<T>,允许服务端写入;- 返回值 → 直接返回
Vec<T>。
也就是说,生成的 Rust 绑定会尽可能使用符合 Rust 惯用法的类型,而不是机械地照搬 Java 数组语义。
同步更新服务端实现(src/android/aidl/birthday_service/src/lib.rs 中的对应实现):
impl IBirthdayService for BirthdayService { fn wishHappyBirthday( &self, name: &str, years: i32, text: &[String], ) -> binder::Result<String> { let mut msg = format!( "Happy Birthday {name}, congratulations with the {years} years!", ); for line in text { msg.push('\n'); msg.push_str(line); } Ok(msg) } }同步更新客户端调用:
let msg = service.wishHappyBirthday( &name, years, &[ String::from("Habby birfday to yuuuuu"), String::from("And also: many more"), ], )?;由于 AIDL 接口变更会导致生成的 trait 签名变化,而 Rust 的强类型系统会在编译期强制服务端与客户端同步更新——只要某一端没有适配新签名,编译就会失败,这从语言层面保证了接口演化的安全。
进阶二:复杂 Binder 类型的实战用法
完整版的IBirthdayService.aidl(src/android/aidl/birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl)与lib.rs/client.rs中还演示了四类更复杂的 Binder 类型,配合 src/android/aidl/types.md 及 src/android/aidl/types/ 目录下的文档学习效果更佳。
1. Parcelable:结构化数据的传参
BirthdayInfo是一个 AIDL parcelable,定义于 src/android/aidl/birthday_service/aidl/com/example/birthdayservice/BirthdayInfo.aidl:
package com.example.birthdayservice; parcelable BirthdayInfo { String name; int years; }接口中对应方法为String wishWithInfo(in BirthdayInfo info);。服务端实现(src/lib.rs)把 parcelable 作为共享引用读取字段:
fn wishWithInfo(&self, info: &BirthdayInfo) -> binder::Result<String> { Ok(format!( "Happy Birthday {}, congratulations with the {} years!", info.name, info.years, )) }客户端构造BirthdayInfo { name: name.clone(), years }直接传参。详细机制可参见 src/android/aidl/types/parcelables.md。
2. 嵌套 Binder 对象:把接口作为参数传递
IBirthdayInfoProvider是一个独立的 AIDL 接口,定义于 src/android/aidl/birthday_service/aidl/com/example/birthdayservice/IBirthdayInfoProvider.aidl:
package com.example.birthdayservice; interface IBirthdayInfoProvider { String name(); int years(); }接口方法wishWithProvider(IBirthdayInfoProvider provider)演示"把一个 Binder 对象传给另一个服务"。客户端实现InfoProvider结构体(src/client.rs),它同样需要实现binder::Interface与生成的IBirthdayInfoProvider,然后通过BnBirthdayInfoProvider::new_binder(...)包装后传给服务端:
let provider = BnBirthdayInfoProvider::new_binder( InfoProvider { name: name.clone(), age: years as u8 }, BinderFeatures::default(), ); service.wishWithProvider(&provider)?;服务端通过provider.name()?、provider.years()?跨进程反向调用客户端传入的 Binder 对象——这是"回调(callback)"模式在 Binder 上的直接体现。生成 trait 路径中IBirthdayInfoProvider与BnBirthdayInfoProvider成对出现(客户端侧还有对应的Bp*代理类型,由生成代码内部使用)。
3. 类型擦除:以 IBinder 形式传递接口
wishWithErasedProvider(IBinder provider)演示先擦除具体类型、拿到SpIBinder后再还原:
fn wishWithErasedProvider(&self, provider: &SpIBinder) -> binder::Result<String> { // Convert the `SpIBinder` to a concrete interface. let provider = provider.clone().into_interface::<dyn IBirthdayInfoProvider>()?; Ok(format!( "Happy Birthday {}, congratulations with the {} years!", provider.name()?, provider.years()?, )) }SpIBinder(strong pointer to IBinder)是不关心具体接口的原始 Binder 句柄;into_interface::<dyn IBirthdayInfoProvider>()在运行时把句柄转换为具体接口,转换失败(类型不匹配)时返回错误。客户端对应调用为service.wishWithErasedProvider(&provider.as_binder())?。
4. ParcelFileDescriptor:跨进程传递文件
wishFromFile(in ParcelFileDescriptor infoFile)演示如何把文件描述符作为 IPC 参数传递。服务端把ParcelFileDescriptor还原为File后读取内容:
fn wishFromFile(&self, info_file: &ParcelFileDescriptor) -> binder::Result<String> { let mut info_file = info_file .as_ref() .try_clone() .map(File::from) .expect("Invalid file handle"); let mut contents = String::new(); info_file.read_to_string(&mut contents).unwrap(); let mut lines = contents.lines(); let name = lines.next().unwrap(); let years: i32 = lines.next().unwrap().parse().unwrap(); Ok(format!("Happy Birthday {name}, congratulations with the {years} years!")) }代码中ParcelFileDescriptor内部包装了一个OwnedFd,通过as_ref().try_clone()克隆文件描述符再构造File对象读取。客户端在设备本地路径写一个两行文本(第一行名字、第二行年龄),再用ParcelFileDescriptor::new(file)包装后发送:
let mut file = File::create("/data/local/tmp/birthday.info").unwrap(); writeln!(file, "{name}")?; writeln!(file, "{years}")?; let file = ParcelFileDescriptor::new(file); service.wishFromFile(&file)?;文件描述符传递的底层细节可参见 src/android/aidl/types/file-descriptor.md。
小结:Rust × Binder 的关键设计模式
回顾 Birthday Service 教程,可以提炼出在 Android 上用 Rust 构建 Binder 服务的核心模式:
| 环节 | 关键 API / 类型 | 作用 |
|---|---|---|
| 接口声明 | .aidl文件 +aidl_interfaceSoong 模块 | 定义跨进程 API 契约,backend.rust.enabled开启 Rust 后端 |
| 生成绑定 | com_example_birthdayservice-rustcrate | 为每个接口生成唯一的 Rust trait,客户端与服务端共用 |
| 服务实现 | impl binder::Interface+impl IBirthdayService | 实现业务逻辑,方法接收&self,可变状态放入Mutex |
| 服务注册 | BnXxx::new_binder+binder::add_service+join_thread_pool | 用组合替代继承包装服务,注册到系统服务管理器并监听 |
| 客户端连接 | binder::get_interface::<dyn IBirthdayService> | 按服务标识符获取Strong<dyn IBirthdayService>并跨进程调用 |
| 设备侧验证 | service check/service call | 绕过类型化客户端,直接验证服务注册状态与 IPC 结果 |
| 复杂类型 | parcelable、嵌套 Binder、SpIBinder、ParcelFileDescriptor | 结构化数据、回调、类型擦除、跨进程文件传递 |
这门课程还配套了 src/android/aidl/types.md(数组、文件描述符、对象、parcelable、基础类型等类型的完整讲解)与 src/android/testing/ 下的测试方案,可以作为继续深入 Rust AIDL 开发的下一站。把上面的示例跑通,你就具备了在 Android 系统级开发中用 Rust 编写、部署和调用 Binder 服务的基本能力。
【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考