
用 Rust 编写 AIDL 服务客户端comprehensive-rust 课程 birthday_client 实战解析【免费下载链接】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-rustAIDLAndroid Interface Definition Language是 Android 上跨进程服务通信的标准机制而本仓库Google Android 团队用于教学的 Rust 课程 comprehensive-rust在src/android/aidl/example-service/client.md中演示了如何用 Rust 编写 AIDL 服务的客户端。本文将围绕该文档结合birthday_service示例的完整源码与Android.bp构建配置讲解 Rust 客户端从接口导入、服务获取、调用到构建部署的完整链路读者学完后能够独立为任意 AIDL 服务编写可运行的 Rust 客户端并理解Strongdyn IBirthdayService背后的 Binder 引用计数模型。AIDL 客户端在整个课程示例中的位置在 comprehensive-rust 的 Android 章节中example-service小节围绕同一个 BirthdayService生日祝福服务串起了一条完整的开发主线interface.md用.aidl文件声明服务接口如wishHappyBirthdayservice.md在src/lib.rs中实现IBirthdayServicetraitserver.md在src/server.rs中注册服务并进入 Binder 线程池deploy.md构建、推送、启动服务并验证client.md本文主体编写 Rust 客户端连接并调用该服务。其中服务端侧接口定义位于 IBirthdayService.aidl客户端最终通过 AIDL 编译器生成的 Rust 绑定与该接口通信。这一节的核心教学点是Rust 客户端不依赖服务实现代码只依赖 AIDL 生成的绑定 crate从而在语言层面天然解耦了跨进程的接口提供者与调用者。客户端完整源码解析文档展示了客户端入口代码见 client.rs 的main函数锚点。为了便于读者完整理解这里给出birthday_service/src/client.rs的关键结构//! Birthday service. use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService; use com_example_birthdayservice::binder::{self, BinderFeatures, ParcelFileDescriptor}; const SERVICE_IDENTIFIER: str birthdayservice; /// Call the birthday service. fn main() - Result(), Boxdyn 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(()) }生成绑定 crate 的导入路径use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService;这一行看似冗长实则每一段都有明确含义com_example_birthdayserviceAIDL 包名com.example.birthdayservice转换成的 Rust crate 名AOSP 的rustlibs机制将 AIDL 生成的绑定暴露为独立 crateaidl::com::example::birthdayservice与.aidl文件中的package com.example.birthdayservice;一一对应的模块路径IBirthdayService.aidlIBirthdayService生成的 Rust trait位于aidl::...::IBirthdayService模块内。正是这种目录结构必须与包名一致的约定interface.md 中明确强调保证了生成代码的路径可以精确推导。获取服务句柄binder::ProcessState::start_thread_pool(); let service binder::get_interface::dyn IBirthdayService(SERVICE_IDENTIFIER) .map_err(|_| Failed to connect to BirthdayService)?;binder::ProcessState::start_thread_pool()为当前进程启动 Binder 线程池。虽然客户端以发起调用为主但 Binder 事务机制要求进程具备处理线程此调用是使用 Binder 的标准前置步骤。binder::get_interface::dyn IBirthdayService(SERVICE_IDENTIFIER)按服务名在系统服务管理器中查找服务并返回 trait object。SERVICE_IDENTIFIER的值birthdayservice必须与服务端注册时使用的标识符完全一致见下文。调用服务方法let msg service.wishHappyBirthday(name, years)?; println!({msg});wishHappyBirthday对应 AIDL 接口中String wishHappyBirthday(String name, int years);IBirthdayService.aidl。在客户端侧它表现为一个普通 Rust 方法调用底层则封装了完整的 Binder 事务封包、跨进程传输、解包对调用方完全透明返回binder::ResultString可用?直接传播错误。命令行参数解析客户端还支持从命令行传入参数便于在设备上演示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);第一个参数为名字缺省Bob第二个参数为年龄需能解析为i32解析失败或缺省时回退为42。这一设计使同一二进制无需重新编译即可验证不同输入也直接对应文档中的运行示例birthday_client Charlie 60。客户端中的高级调用形态除文档展示的wishHappyBirthday之外仓库中的 client.rs 还演示了另外三种 AIDL 调用形态它们与 AIDL 接口中声明的多种参数类型一一对应IBirthdayService.aidl1. 传递 ParcelablewishWithInfoservice.wishWithInfo(BirthdayInfo { name: name.clone(), years })?;BirthdayInfo是从 AIDLparcelable类型生成的 Rust 结构体通过引用传入对应接口声明String wishWithInfo(in BirthdayInfo info);。2. 传递 Binder 对象wishWithProvider/wishWithErasedProviderlet provider BnBirthdayInfoProvider::new_binder( InfoProvider { name: name.clone(), age: years as u8 }, BinderFeatures::default(), ); service.wishWithProvider(provider)?; service.wishWithErasedProvider(provider.as_binder())?;客户端本地实现了一个IBirthdayInfoProvidertrait 的InfoProvider结构体将其包装为 Binder 对象后发给服务端服务端再回调读取姓名与年龄。这是 AIDL 中回调接口模式的雏形。其中BnBirthdayInfoProvider::new_binder(...)生成该接口的服务端风格 Binder 节点Bn前缀表示 binder native 端wishWithErasedProvider(provider.as_binder())将 trait object 擦除为通用IBinder传递对应 AIDL 中String wishWithErasedProvider(IBinder provider);演示了跨进程传递类型已擦除的 Binder 句柄的能力。3. 传递文件描述符wishFromFilelet mut file File::create(/data/local/tmp/birthday.info).unwrap(); writeln!(file, {name})?; writeln!(file, {years})?; let file ParcelFileDescriptor::new(file); service.wishFromFile(file)?;客户端将生日信息写入本地文件再把文件包装成ParcelFileDescriptor发送给服务端。服务端在 lib.rs 的wishFromFile中通过try_clone()复制描述符并读取内容展示了 AIDL 传递文件描述符的典型用法。构建配置birthday_client 的 Android.bp文档明确指出客户端不依赖libbirthdayservice。这一点在 birthday_service/Android.bp 中体现得十分直观// ANCHOR: birthday_client 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. } // ANCHOR_END: birthday_client对比同文件中服务端的配置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. }关键差异一目了然构建目标srcsrustlibs是否依赖服务实现birthday_clientsrc/client.rs仅com.example.birthdayservice-rust否birthday_serversrc/server.rscom.example.birthdayservice-rustlibbirthdayservice是com.example.birthdayservice-rust是由aidl/下的 AIDL 文件与 Android.bp 生成绑定 crate 的构建目标客户端只需它即可获得 trait 定义libbirthdayservice是服务实现的 rust_libraryAndroid.bp 中的rust_library目标srcs 为src/lib.rs只有服务端才需要链接prefer_rlib: true注释标明 To avoid dynamic link error即优先使用静态 rlib 链接以避免动态链接错误这一点客户端与服务端一致。正是这种依赖隔离使得换一个接口实现即可无缝替换服务端客户端零改动成为可能——接口IBirthdayServicetrait是客户端与服务端之间唯一的契约。构建、推送与运行文档提供了完整的设备侧验证流程源自 build_all.sh 中的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输出Happy Birthday Charlie, congratulations with the 60 years!运行前置条件该命令序列依赖完整的 AOSP 构建环境参照 build_all.sh 头部的说明脚本必须在 AOSP checkout 中执行且已配置好可用的adb需要通过 bind mount 或直接克隆将本仓库src/挂载到 AOSP 的comprehensive-rust/目录使m能看到新的构建目标脚本内部会先完成环境初始化与lunch选择如aosp_cf_x86_64_phone-userdebug再依次构建、推送并启动birthday_server服务端启动后会等待adb shell service check birthdayservice返回found随后才执行客户端。服务端与客户端的联动deploy.md 展示了服务端侧的验证方式adb shell service check birthdayservice确认服务已注册adb shell service call birthdayservice 1 s16 Bob i32 24可直接以原始 Parcel 方式调用接口方法 1即wishHappyBirthday。而 Rust 客户端则通过binder::get_interface以类型安全的方式完成同样调用两者可以互为印证前者验证了服务的原生可调用性后者验证了 Rust 绑定的正确性。服务标识符的一致性SERVICE_IDENTIFIER在客户端client.rs与服务端server.rs中都是字符串birthdayservice// server.rs binder::add_service(SERVICE_IDENTIFIER, birthday_service_binder.as_binder()) .expect(Failed to register service);服务端用binder::add_service注册客户端用binder::get_interface按同名查找二者必须精确匹配。文档特别建议该标识符最好定义在客户端与服务端都能依赖的公共 crate 中以避免字符串硬编码在不同模块中漂移失配。理解Strongdyn IBirthdayServiceBinder 的引用计数模型文档用details详细解释了客户端拿到的服务句柄类型Strongdyn IBirthdayService这是理解 Rust 版 Binder 绑定最核心的概念Strong 是 Binder 专用的智能指针Strong是 binder crate 为 Binder 对象定制的智能指针类型smart pointer type它维护着两套引用计数进程内引用计数in-process ref count用于管理服务 trait object 在本地进程中的生命周期保证dyn IBirthdayService被安全持有和释放全局 Binder 引用计数global Binder ref count跟踪有多少个进程持有了对这个 Binder 对象的引用这是 Android Binder 驱动层面跨进程生命周期的依据。也就是说Strong同时承担了本地 RAII 所有权与跨进程引用记账两个职责当最后一个引用释放时两端计数都会正确归零。客户端与服务端共享同一个 trait值得特别强调的是客户端用来与服务通信的 trait object与服务端实现的 trait 是完全相同的 trait。对于给定的一个 Binder 接口AIDL 编译器只生成一个 Rust trait客户端和服务端都基于它编程服务端impl IBirthdayService for BirthdayServicelib.rs并经由BnBirthdayService::new_binder(...)生成 Binder 节点客户端binder::get_interface::dyn IBirthdayService(...)获取远端的 trait object 并进行动态调用。这种单一 trait、双端复用的设计是 Rust AIDL 绑定区别于 C/Java 的关键优势之一接口契约在类型系统中就保证了双端一致任何接口定义变更都会同时在编译期暴露给两端。若需要修改接口定义可参照 changing-definition.md 与 changing-implementation.md 中的流程同步更新两端。从入门到实战客户端开发要点总结综合文档与源码编写 Rust AIDL 客户端时应把握以下要点只依赖生成绑定客户端rustlibs只需com.example.birthdayservice-rust生成绑定绝不链接服务实现库libbirthdayservice启动 Binder 线程池调用binder::ProcessState::start_thread_pool()这是使用 Binder 的标准前置步骤按标识符获取服务binder::get_interface::dyn IFoo(SERVICE_IDENTIFIER)标识符与服务端add_service时严格一致并建议提取到公共 crate类型安全调用AIDL 方法映射为 Rust trait 方法返回binder::ResultT可用?传播错误多种参数形态Parcelable、嵌套 Binder 对象回调、IBinder擦除、ParcelFileDescriptor均可作为参数跨进程传递对应示例分别见wishWithInfo、wishWithProvider、wishWithErasedProvider、wishFromFile构建部署闭环m构建 →adb push推送 →adb shell执行验证输出与服务端行为一致。通过 example-service 这一完整系列接口定义 → 服务实现 → 服务注册 → 部署验证 → 客户端调用课程向读者展示了用 Rust 全链路构建 Android AIDL 服务的范式。client.md 作为这条链路的收尾恰好演示了 Rust 客户端如何以最少的依赖、最安全的类型抽象完成对跨进程服务的消费。【免费下载链接】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),仅供参考