comprehensive-rust 教程:在 AOSP 中构建、部署与调用 Rust Binder 服务(birthday_server 实战指南)
【免费下载链接】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 课程中 Android AIDL 章节的deploy一课为核心,完整讲解如何把用 Rust 编写的 Binder 服务birthday_server构建、推送到设备并启动,再通过service check与service call验证其可用性。读完本文,你将掌握 AOSP Soong 构建、adb部署、服务注册检查与 Parcel 结果解析的完整闭环,能够独立将课程中的 Birthday Service 示例跑通。
背景:我们要部署什么
在课程中,Birthday Service 是一套完整的"接口 — 服务实现 — 服务端 — 客户端"示例,用于演示用 Rust 调用 Android Binder。整个流程由 src/android/aidl/birthday-service.md 起头,依次经过:
- 定义 AIDL 接口
IBirthdayService(见 src/android/aidl/birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl); - 在 Rust 中实现该接口(src/android/aidl/birthday_service/src/lib.rs);
- 编写服务端进程
birthday_server(src/android/aidl/birthday_service/src/server.rs)与客户端birthday_client(src/android/aidl/birthday_service/src/client.rs)。
而deploy这一步要解决的是:当代码写完之后,如何在真实 AOSP 环境里把它变成设备上可用的系统服务。deploy.md原文(src/android/aidl/example-service/deploy.md)给出的核心操作只有"build、push、start"三个词,但其背后依赖的构建脚本与 Binder 注册机制,正是本文要展开的部分。
部署前置条件
在运行部署命令之前,需要满足以下环境要求(依据课程构建脚本 src/android/build_all.sh 的头部注释整理):
- AOSP 源码树:脚本必须在 AOSP checkout 中执行,且需要完成
source build/envsetup.sh与lunch(课程示例使用aosp_cf_x86_64_phone-userdebug目标,即 Cuttlefish 虚拟设备镜像); - 可用的
adb环境:已连接设备或模拟器,adb正常工作; - 让
m能看到课程源码:AOSP 的构建系统只能看到放在源码树内的Android.bp,因此需要把课程仓库放进 AOSP checkout,或用 bind mount 挂载,例如:
cd "$ANDROID_BUILD_TOP" mkdir comprehensive-rust sudo mount -o bind ../path/to/comprehensive-rust/src comprehensive-rust挂载后即可用m hello_rust之类的目标验证构建系统是否识别课程中的模块。build_all.sh中所有m目标(如birthday_server、birthday_client、hello_rust等)正是依赖这种方式才能被 AOSP 发现并编译。
第一步:构建并启动服务
deploy.md中引用的构建、推送、启动命令,实际来自 src/android/build_all.sh 中birthday_server函数内的ANCHOR: birthday_server代码段。原文档通过 mdBook 的{{#include}}机制将脚本片段嵌入页面,展开后的完整命令如下:
m birthday_server adb push "$ANDROID_PRODUCT_OUT/system/bin/birthday_server" /data/local/tmp adb root adb shell /data/local/tmp/birthday_server逐条解释:
| 命令 | 作用 |
|---|---|
m birthday_server | 调用 Soong 构建系统编译birthday_server这个rust_binary目标,产物输出到$ANDROID_PRODUCT_OUT/system/bin/ |
adb push ... /data/local/tmp | 把编译出的二进制推送到设备上的/data/local/tmp目录 |
adb root | 以 root 身份重启 adbd。/data/local/tmp需要 root 权限运行,且注册 Binder 系统服务通常也需要较高权限 |
adb shell /data/local/tmp/birthday_server | 在设备上直接启动服务进程,它会在前台运行并开始监听 Binder 请求 |
需要特别注意的是:birthday_server是一个前台进程,启动后会一直占用终端(因为服务端调用了join_thread_pool阻塞等待请求)。因此在build_all.sh中,脚本用birthday_server &将其放到后台运行并记录 PID,部署一节也要求"在另一个终端中检查服务状态"。这与客户端birthday_client不同——后者是短生命周期程序,调用完即退出。
构建目标对应的 Soong 配置
m birthday_server之所以能够成立,是因为 src/android/aidl/birthday_service/Android.bp 中定义了对应的 Soong 模块:
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. }关键点:
rustlibs引用了两个依赖:com.example.birthdayservice-rust(AIDL 接口生成的 Rust crate)与libbirthdayservice(服务实现所在的rust_library,定义见同文件ANCHOR: libbirthdayservice段);prefer_rlib: true注释明确说明是为了避免动态链接错误,即要求静态链接 Rust 库,保证二进制在设备上可独立运行;- AIDL 接口 crate 本身由 src/android/aidl/birthday_service/aidl/Android.bp 中的
aidl_interface模块生成,其中backend.rust.enabled = true表明Rust 后端默认未启用,需要显式打开,这是 Rust AIDL 与 Java/C++ 后端的显著差异。
第二步:用service check验证服务已注册
服务启动后,在另一个终端执行service check确认它已经被 Android 服务管理器(ServiceManager)登记:
adb shell service check birthdayservice预期输出:
Service birthdayservice: found这里birthdayservice正是服务端代码 src/android/aidl/birthday_service/src/server.rs 中定义的注册名:
const SERVICE_IDENTIFIER: &str = "birthdayservice"; ... binder::add_service(SERVICE_IDENTIFIER, birthday_service_binder.as_binder()) .expect("Failed to register service");service check返回found意味着注册成功,也印证了部署流程中"启动服务 → 注册到 ServiceManager"这一环是通的。build_all.sh中还有一个配套细节:脚本在启动birthday_server后会用循环等待服务真正注册完成:
while adb shell service check birthdayservice | grep -q 'not found'; do echo "Waiting on birthdayservice..." sleep 3 done这说明服务注册是异步过程——进程启动后需要一点时间完成 Binder 初始化与add_service调用,立即service check可能得到not found。
服务端注册的完整机制
server.rs的main函数完整展示了 Rust Binder 服务的注册四步曲(deploy 的"start"环节能成立的原因):
fn main() { let birthday_service = BirthdayService; // 1. 创建服务实例 let birthday_service_binder = BnBirthdayService::new_binder( // 2. 包装为 Bn* 类型 birthday_service, binder::BinderFeatures::default(), ); binder::add_service(SERVICE_IDENTIFIER, birthday_service_binder.as_binder()) // 3. 注册 .expect("Failed to register service"); binder::ProcessState::join_thread_pool(); // 4. 加入线程池 }BirthdayService是 src/android/aidl/birthday_service/src/lib.rs 中实现IBirthdayServicetrait 的类型;BnBirthdayService由 AIDL 编译器生成,相当于 C++ Binder 世界中的BnBinder基类角色;由于 Rust 没有继承,这里通过组合把自定义服务实例放进生成的BnBirthdayService中;binder::add_service将服务以字符串标识注册进 ServiceManager;join_thread_pool让当前线程加入 Binder 线程池开始监听连接——这正是服务端进程不退出、持续响应请求的原因。
课程中还特别强调:wishHappyBirthday等 AIDL 方法签名是&self而非&mut self,因为 Binder 在线程池中并行处理多个请求,服务方法只能拿到共享引用;若服务需要可变状态,应放入Mutex之类的同步原语中。
第三步:用service call直接调用接口方法
deploy.md进一步演示了不写客户端代码、直接用service call从 shell 调用 Binder 方法的技巧:
adb shell service call birthdayservice 1 s16 Bob i32 24该命令的参数含义为:
| 参数 | 含义 |
|---|---|
birthdayservice | 服务注册名(与service check一致) |
1 | 调用的接口方法索引:AIDL 接口中方法从 1 开始编号,1对应IBirthdayService中第一个方法wishHappyBirthday(见 IBirthdayService.aidl 中方法声明顺序) |
s16 Bob | 第一个参数,UTF-16 字符串"Bob",对应String name |
i32 24 | 第二个参数,32 位整数24,对应int years |
返回结果是原始 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.!..... ')这段转储的可读文本列(右侧 ASCII 区)把每个 16 位字解码后拼出了:
Happy Birthday Bob, congratulations with the 24 years!这正是lib.rs中wishHappyBirthday的实现逻辑:
fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String> { Ok(format!("Happy Birthday {name}, congratulations with the {years} years!")) }同时也可以从转储中看到 Binder 字符串的编码特点:文本以 UTF-16 存储(注意00610048等每个字符占 2 字节),00000036是长度为 0x36=54 个字符的数据长度前缀。理解这条命令与输出,就能在没有客户端的情况下快速验证接口的行为,是排查服务端逻辑问题的利器。
方法索引的确定方式
方法索引1不是随意取的。对照 IBirthdayService.aidl:
interface IBirthdayService { String wishHappyBirthday(String name, int years); // transaction code 1 String wishWithInfo(in BirthdayInfo info); // 2 String wishWithProvider(IBirthdayInfoProvider provider); // 3 String wishWithErasedProvider(IBinder provider); // 4 String wishFromFile(in ParcelFileDescriptor infoFile); // 5 }wishHappyBirthday位于首位,故交易码为1。若要调用其他方法,需要相应调整索引,并注意其参数类型的编码(例如wishWithInfo需要传入 parcelable,wishFromFile需要传文件描述符)。
部署后的完整验证链路
deploy.md之外,build_all.sh还给出了与部署配套的客户端验证方式(属于ANCHOR: birthday_client段),可作为部署成功的最终确认:
m birthday_client adb push "$ANDROID_PRODUCT_OUT/system/bin/birthday_client" /data/local/tmp adb shell /data/local/tmp/birthday_client Charlie 60客户端源码(src/android/aidl/birthday_service/src/client.rs)通过binder::get_interface::<dyn IBirthdayService>(SERVICE_IDENTIFIER)获取服务端接口,然后调用wishHappyBirthday(&name, years)打印结果。与service call相比,客户端走的是类型安全的 Rust API,而非手写交易码。
部署与验证的完整时序可归纳为:
m birthday_server构建;adb push+adb shell .../birthday_server启动服务(前台/后台皆可);adb shell service check birthdayservice轮询直到输出found;- 选择验证方式:
- 快速验证:
adb shell service call birthdayservice 1 s16 Bob i32 24; - 类型安全验证:编译并运行
birthday_client;
- 快速验证:
- 验证结束,杀掉服务进程(
build_all.sh中为pkill -f birthday_server)。
常见问题与排查要点
结合构建脚本与源码,部署环节最容易踩的坑集中在以下几点:
m birthday_server找不到目标:课程仓库未挂载进 AOSP checkout。按前置条件一节执行 bind mount,并确认m hello_rust能正常工作;service check长时间not found:服务可能未启动成功或注册名拼写不一致。核对SERVICE_IDENTIFIER常量(birthdayservice)与命令中的名称是否一致;也可检查服务进程是否因 panic 崩溃(add_service失败会触发expect直接退出);service call返回异常 Parcel:交易码或参数类型写错。核对方法索引与 AIDL 声明顺序,确认参数类型标记(s16、i32)与方法签名匹配;- 动态链接错误:若未设置
prefer_rlib: true,设备上可能缺少对应的动态库。这是 birthday_service/Android.bp 注释中明确提示的风险。
小结
deploy一课打通了 Rust Binder 服务从"代码"到"设备上可调用服务"的最后一公里:通过m完成 Soong 构建,用adb推送并启动birthday_server,用service check确认服务注册,用service call完成免客户端的接口级验证。整个过程背后是 AIDL 的 Rust 后端、BnBirthdayService组合包装、add_service注册与join_thread_pool线程池监听等机制在支撑——这些机制的具体实现,均可在 src/android/aidl/birthday_service 目录与 src/android/build_all.sh 中找到对应源码。跑通本教程后,你便掌握了在 AOSP 中部署任意 Rust Binder 服务的通用套路:定义接口、实现服务、编译部署、shell 验证。
【免费下载链接】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),仅供参考