ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

在 Android 上使用 Rust 与 Binder:Birthday Service 完整实战教程

在 Android 上使用 Rust 与 Binder:Birthday Service 完整实战教程 在 Android 上使用 Rust 与 BinderBirthday 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 中使用 AIDLAndroid 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.aidlpackage 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.bpaidl_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-rustsrcs使用通配符收集com/example/birthdayservice/下的所有.aidl文件unstable: true教学示例通过它绕开对已发布frozenAIDL 接口的版本限制实际产品代码应遵循严格的接口版本管理流程backend.rust.enabled: trueRust 后端默认并不启用必须显式打开才会生成 Rust 绑定代码。这正是Rust 不是 AIDL 默认后端这一事实在构建配置层面的直接体现。第二步查看 Binder 为接口生成的 Rust APIAIDL 编译器会为每个接口定义生成一个 Rust trait。讲解文档 src/android/aidl/example-service/service-bindings.md 给出了经过清理和简化的生成代码形态真实代码生成在构建输出目录out/soong/.intermediates/.../com_example_birthdayservice.rstrait IBirthdayService { fn wishHappyBirthday(self, name: str, years: i32) - binder::ResultString; }关键信息生成的 trait 名与 AIDL 接口名一致客户端与服务端使用的是同一个 trait服务端实现它客户端通过它发起调用每个 IPC 方法都接收self共享引用而不是mut self原因是 Binder 在线程池上并发处理多个请求服务方法只能拿到self的共享引用返回类型统一包裹为binder::ResultTIPC 错误如服务未注册、类型不匹配通过 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::ResultString { Ok(format!(Happy Birthday {name}, congratulations with the {years} years!)) } }实现要点必须同时实现两个 traitbinder::InterfaceBinder 框架要求的基础接口和生成的IBirthdayService业务接口BirthdayService是一个零字段的结构体unit struct因为当前方法无需内部状态方法体用format!拼接祝福消息并通过Ok(...)返回错误类型直接沿用binder::Result。状态管理为什么方法接收 self 而不是 mut self课程文档在 src/android/aidl/example-service/implementation.md 中强调了一个重要设计约束Binder 在线程池上响应请求可能并行处理多个 IPC 调用因此服务方法只能获得self的共享引用。如果服务需要维护可变状态就必须把状态放入Mutex等同步原语中以保证安全修改例如在BirthdayService中增加MutexCounter之类的字段来统计调用次数具体采用哪种并发方案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.bprust_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_librarylibbirthdayservice与rust_binarybirthday_server分离职责清晰两者都依赖 AIDL 生成的 Rust cratecom.example.birthdayservice-rustprefer_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 字符串Bobs16和 32 位整数24i32。预期返回的 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.rsuse 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(), 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(()) }客户端流程分四步解析命令行参数从argv[1]读取名字缺省Bob、从argv[2]读取年龄缺省42演示了 Rust 标准库的参数处理惯用法binder::ProcessState::start_thread_pool()客户端进程也需要启动 Binder 线程池以接收来自 Binder 驱动的回调binder::get_interface::dyn IBirthdayService(SERVICE_IDENTIFIER)按服务标识符向系统服务管理器查询并建立连接返回Strongdyn IBirthdayServicetrait 对象连接失败时通过map_err转成错误信息发起调用service.wishHappyBirthday(name, years)??运算符把 IPC 错误向上传播。StrongBinder 的自定义智能指针课程文档在 src/android/aidl/example-service/client.md 中对Strongdyn IBirthdayService做了专门说明Strong是 Binder 的自定义智能指针类型同时维护两类引用计数进程内的引用计数对应 Rust trait 对象的生命周期与全局 Binder 引用计数记录有多少进程持有了这个 Binder 对象的引用客户端与服务端使用的 trait是同一个生成的 trait——对某个 Binder 接口而言无论客户端还是服务端Rust 编译器都只生成一个 trait客户端把它当作远端对象的接口服务端把它当作本地对象的实现契约客户端使用的服务标识符必须与注册时一致。教程还给出工程化建议这个标识符最好定义在客户端和服务端都能依赖的公共 crate 中避免字符串常量在不同模块间漂移失配。客户端 Soong 配置客户端的构建配置同样位于 src/android/aidl/birthday_service/Android.bprust_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::ResultString; }课程文档总结了in/out/inout数组参数与返回值在 Rust 绑定中的通用映射规律in数组参数 → Rust 切片[T]只读借用out与inout参数 →mut VecT允许服务端写入返回值 → 直接返回VecT。也就是说生成的 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::ResultString { 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.aidlsrc/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.aidlpackage com.example.birthdayservice; parcelable BirthdayInfo { String name; int years; }接口中对应方法为String wishWithInfo(in BirthdayInfo info);。服务端实现src/lib.rs把 parcelable 作为共享引用读取字段fn wishWithInfo(self, info: BirthdayInfo) - binder::ResultString { 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.aidlpackage 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::ResultString { // 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()?, )) }SpIBinderstrong 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::ResultString { 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::Interfaceimpl IBirthdayService实现业务逻辑方法接收self可变状态放入Mutex服务注册BnXxx::new_binderbinder::add_servicejoin_thread_pool用组合替代继承包装服务注册到系统服务管理器并监听客户端连接binder::get_interface::dyn IBirthdayService按服务标识符获取Strongdyn 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),仅供参考
返回列表