)
Comprehensive Rust 实战在 Rust 中处理 AIDL 类型Primitive、数组、Binder 对象、文件描述符与 Parcelable【免费下载链接】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导读本篇基于 Google Android 团队的 Rust 课程Comprehensive Rust中 AIDL 类型章节系统讲解 Android 接口定义语言AIDL中的各种数据类型如何被翻译为符合 Rust 习惯的对应类型。你将掌握 Primitive 类型、数组/集合类型、AIDL 对象引用、ParcelFileDescriptor文件句柄以及 Parcelable 自定义类型在 Rust 与 Binder 进程间通信中的映射规则与实战用法并能基于仓库中的birthday_service完整示例独立实现自己的 AIDL 服务与客户端。一、AIDL 类型映射总览AIDL 是 Android 系统中用于进程间通信IPC的接口定义语言。在 Rust 生态中AIDL 类型会翻译为相应的、符合 Rust 习惯idiomatic的类型总体遵循以下四条规则Primitive 类型基本映射为 Rust 惯用类型个别有符号性/位宽差异需特别注意集合类型切片slices、Vec与字符串类型均被支持对象引用对 AIDL 对象及文件句柄的引用可以在客户端与服务端之间传递文件句柄与 Parcelable文件句柄ParcelFileDescriptor与自定义 Parcelable 得到完整支持。课程 AIDL 章节 指出Rust 对 AIDL 的支持意味着Rust 代码既可以调用已有的 AIDL 服务端也可以在 Rust 中创建新的 AIDL 服务端。由于 Rust 是该生态中的一等公民first-class citizen设备上的其他进程可以直接调用 Rust 编写的服务。以下各节将逐类展开说明其映射规则与代码形态。二、Primitive 类型一套基本映射、个别注意的对照表AIDL 的标量类型到 Rust 的映射如下详见 primitives.mdAIDL 类型Rust 类型备注booleanbool无bytei8注意字节在 AIDL 中是有符号的charu16注意是u16不是u32inti32无longi64无floatf32无doublef64无StringString无需要特别记忆的两个坑byte映射为有符号的i8。这与许多开发者对字节的无符号直觉相反。在 AIDL 的 Java 后端中byte本身即是有符号 8 位整数因此 Rust 端对应i8是语义一致的。char映射为u16而非u32。AIDL 的char本质上是 UTF-16 编码单元因此占 16 位对应 Rust 的u16。若误写为u32或charRust 的char是 32 位 Unicode 标量值会导致类型不匹配。源码印证在仓库的 birthday_service 示例中接口方法wishHappyBirthday(String name, int years)在 Rust 端被实现为impl IBirthdayService for BirthdayService { fn wishHappyBirthday(self, name: str, years: i32) - binder::ResultString { Ok(format!(Happy Birthday {name}, congratulations with the {years} years!)) } }见 birthday_service/src/lib.rs。可以看到String以str形式传入实现方法int对应i32返回值String对应binder::ResultString。客户端调用时同样符合 Rust 的引用语义let msg service.wishHappyBirthday(name, years)?;见 birthday_service/src/client.rs。三、数组与集合类型按参数位置决定 Rust 形态AIDL 中的数组类型T[]、byte[]、ListT会根据在函数签名中的使用位置被翻译为不同的 Rust 类型详见 arrays.md位置Rust 类型in参数[T]out/inout参数mut VecT返回值VecT这套映射体现了 Rust 所有权与可变性设计在 IPC 接口中的落地in参数只读借用数据从调用方流向服务方服务方无需修改因此只读切片[T]最合适out/inout参数可变借用服务方需要向调用方回填数据因此需要可变引用mut VecT返回值拥有所有权返回值自然用VecT表达完整的拥有关系。此外还有两条补充规则原文档details中的内容Android 13 及以上版本支持定长数组即T[N]会映射为[T; N]定长数组支持多维形式例如int[3][4]。在 Java 后端中定长数组仍然表示为数组类型。Parcelable 字段中的数组一律翻译为VecT不随函数参数位置变化。四、在 Binder 间传递对象具体类型与类型擦除的IBinderAIDL 对象可以通过两种形态在客户端与服务端之间传递作为具体的 AIDL 接口类型或作为类型擦除后的IBinder接口详见 objects.md。仓库的 birthday_service 示例同时演示了这两种用法。4.1 定义被传递的接口IBirthdayInfoProvider是一个只读信息提供接口定义见 IBirthdayInfoProvider.aidlpackage com.example.birthdayservice; interface IBirthdayInfoProvider { String name(); int years(); }4.2 在服务接口中引用它IBirthdayService提供了两个姊妹方法——一个接收具体接口类型另一个接收类型擦除的IBinder见 IBirthdayService.aidlimport com.example.birthdayservice.IBirthdayInfoProvider; interface IBirthdayService { /** The same thing, but using a binder object. */ String wishWithProvider(IBirthdayInfoProvider provider); /** The same thing, but using IBinder. */ String wishWithErasedProvider(IBinder provider); }4.3 客户端创建 Binder 对象并发送在客户端首先定义一个实现该接口的 Rust 结构体并通过binder::Interfacetrait 标记它是可绑定对象见 birthday_service/src/client.rs/// Rust struct implementing the IBirthdayInfoProvider interface. struct InfoProvider { name: String, age: u8, } impl binder::Interface for InfoProvider {} impl IBirthdayInfoProvider for InfoProvider { fn name(self) - binder::ResultString { Ok(self.name.clone()) } fn years(self) - binder::Resulti32 { Ok(self.age as i32) } }然后使用BnBirthdayInfoProvider::new_binder将其包装为 Binder 对象分别以具体类型和SpIBinder两种方式发送见 birthday_service/src/client.rs// Create a binder object for the IBirthdayInfoProvider interface. let provider BnBirthdayInfoProvider::new_binder( InfoProvider { name: name.clone(), age: years as u8 }, BinderFeatures::default(), ); // Send the binder object to the service. service.wishWithProvider(provider)?; // Perform the same operation but passing the provider as an SpIBinder. service.wishWithErasedProvider(provider.as_binder())?;原文档特别提醒details中BnBirthdayInfoProvider与之前课程中见过的BnBirthdayService作用完全相同——它是生成的服务端Bn包装类型用于将一个 Rust 对象暴露为可跨进程传递的 Binder 对象。4.4 服务端接收并反序列化服务端BirthdayService分别实现两个方法见 birthday_service/src/lib.rsfn wishWithProvider( self, provider: Strongdyn IBirthdayInfoProvider, ) - binder::ResultString { Ok(format!( Happy Birthday {}, congratulations with the {} years!, provider.name()?, provider.years()?, )) } 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()?, )) }这里有两个关键点值得展开具体类型形态接收参数为Strongdyn IBirthdayInfoProviderStrong是 binder crate 中对 Binder 强引用的封装可以直接在其上调用接口方法每个方法返回binder::ResultT可跨进程执行。类型擦除形态IBinder对应 Rust 端的SpIBinder。它本身不携带具体接口类型信息需要通过into_interface::dyn IBirthdayInfoProvider()将其还原为具体接口。这是类型安全的动态转换若底层对象实际不实现该接口转换会返回错误而非 UB。五、发送文件ParcelFileDescriptor与文件句柄传递文件可以通过ParcelFileDescriptor类型在 Binder 客户端/服务端之间传递详见 file-descriptor.md。5.1 接口定义IBirthdayService中加载文件信息的方法见 IBirthdayService.aidl/** The same thing, but loads info from a file. */ String wishFromFile(in ParcelFileDescriptor infoFile);5.2 客户端从File创建ParcelFileDescriptor客户端将生日信息写入本地临时文件然后包装为ParcelFileDescriptor发送见 birthday_service/src/client.rs// Open a file and put the birthday info in it. let mut file File::create(/data/local/tmp/birthday.info).unwrap(); writeln!(file, {name})?; writeln!(file, {years})?; // Create a ParcelFileDescriptor from the file and send it. let file ParcelFileDescriptor::new(file); service.wishFromFile(file)?;5.3 服务端从ParcelFileDescriptor还原为File服务端在wishFromFile实现中把收到的文件描述符还原为File并读取内容见 birthday_service/src/lib.rsfn wishFromFile( self, info_file: ParcelFileDescriptor, ) - binder::ResultString { // Convert the file descriptor to a File. ParcelFileDescriptor wraps // an OwnedFd, which can be cloned and then used to create a File // object. 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因此它既可以从File或任何其他包装了OwnedFd的类型创建也可以在接收端用于创建新的File句柄。上述服务端代码正是通过as_ref().try_clone()克隆底层OwnedFd再File::from还原为文件对象。不止普通文件其他类型的文件描述符同样可以被包装发送例如 TCP、UDP 和 UNIX socket。这意味着 AIDL 的ParcelFileDescriptor是通用的 FD 传递通道而不仅限于磁盘文件。六、Parcelable自定义结构化数据的直接传输Binder for Rust 支持直接发送 Parcelable 类型详见 parcelables.md。Parcelable 是 AIDL 中用于表达结构化数据的自定义类型与 Java 的 Serializable 类似但针对 Binder 传输做了优化。6.1 定义 ParcelableBirthdayInfo是一个包含两个字段的 Parcelable定义见 BirthdayInfo.aidlpackage com.example.birthdayservice; parcelable BirthdayInfo { String name; int years; }6.2 在接口中引用服务接口通过in方向参数接收它见 IBirthdayService.aidlimport com.example.birthdayservice.BirthdayInfo; interface IBirthdayService { /** The same thing, but with a parcelable. */ String wishWithInfo(in BirthdayInfo info); }6.3 客户端构造与发送客户端像构造普通 Rust 结构体一样构造BirthdayInfo并直接调用fn main() { binder::ProcessState::start_thread_pool(); let service connect().expect(Failed to connect to BirthdayService); let info BirthdayInfo { name: Alice.into(), years: 123 }; service.wishWithInfo(info)?; }见 birthday_service/src/client.rs 附近的调用。值得注意的是BirthdayInfo是 AIDL 编译器从.aidl文件生成的 Rust 类型客户端代码通过如下路径导入use com_example_birthdayservice::aidl::com::example::birthdayservice::BirthdayInfo::BirthdayInfo;见 birthday_service/src/client.rs生成 crate 名为com_example_birthdayservice由Android.bp的aidl_interface模块编译产物。6.4 服务端接收服务端实现wishWithInfo时参数形态是BirthdayInfo字段可直接访问fn wishWithInfo(self, info: BirthdayInfo) - binder::ResultString { Ok(format!( Happy Birthday {}, congratulations with the {} years!, info.name, info.years, )) }见 birthday_service/src/lib.rs。与 Java/Kotlin 中的 Parcelable 需要手写writeToParcel/createFromParcel不同Rust 端完全由代码生成器负责序列化与反序列化开发者只需面向生成的结构体编程读写字段即可。七、综合回顾一份 AIDL 接口的完整类型全景将 IBirthdayService.aidl 中全部方法汇总即可看到 AIDL 类型系统在 Rust 中的完整映射/** Birthday service interface. */ interface IBirthdayService { /** Generate a Happy Birthday message. */ String wishHappyBirthday(String name, int years); /** The same thing, but with a parcelable. */ String wishWithInfo(in BirthdayInfo info); /** The same thing, but using a binder object. */ String wishWithProvider(IBirthdayInfoProvider provider); /** The same thing, but using IBinder. */ String wishWithErasedProvider(IBinder provider); /** The same thing, but loads info from a file. */ String wishFromFile(in ParcelFileDescriptor infoFile); }对应的 Rust 服务端方法签名一览AIDL 方法Rust 方法签名impl IBirthdayServicewishHappyBirthday(String, int)fn wishHappyBirthday(self, name: str, years: i32) - binder::ResultStringwishWithInfo(in BirthdayInfo)fn wishWithInfo(self, info: BirthdayInfo) - binder::ResultStringwishWithProvider(IBirthdayInfoProvider)fn wishWithProvider(self, provider: Strongdyn IBirthdayInfoProvider) - binder::ResultStringwishWithErasedProvider(IBinder)fn wishWithErasedProvider(self, provider: SpIBinder) - binder::ResultStringwishFromFile(in ParcelFileDescriptor)fn wishFromFile(self, info_file: ParcelFileDescriptor) - binder::ResultString这一对照表完整覆盖了本课程 AIDL 类型章节 归纳的四类情况Primitive 与字符串String/int直接对应str/i32返回值统一包装在binder::ResultT中表达跨进程调用错误集合与数组in参数为[T]out/inout为mut VecT返回值为VecTParcelable 字段固定为VecTAndroid 13 支持[T; N]定长与多维数组对象引用具体接口对应Strongdyn Trait类型擦除的IBinder对应SpIBinder可用into_interface安全还原文件句柄与 ParcelableParcelFileDescriptor包装OwnedFd用于跨进程传递文件/socket 等句柄Parcelable 生成的结构体可直接构造、读写与传递。八、延伸阅读与实操路径想了解 Rust 服务端如何注册并被客户端发现可继续阅读课程 AIDL 章节 及其下的 birthday-service.md完整的可运行示例位于 birthday_service 目录其中 AIDL 定义在 aidl/com/example/birthdayserviceRust 客户端、服务端与库实现分别在 client.rs、server.rs 和 lib.rs如需进一步了解 Binder for Rust 的进程初始化如binder::ProcessState::start_thread_pool()与服务注册流程可参考同目录下的 service-bindings.md 等文档。掌握上述映射规则后你就能够在使用 Rust 编写 Android 系统服务或调用既有 AIDL 服务时准确预判每种 AIDL 类型在 Rust 端的形态避免类型不匹配写出符合 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),仅供参考