news 2026/9/10 16:09:24

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 Android 上使用 Rust 与 Binder:Birthday Service 完整实战教程

在 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:调用方传入名字与年龄,服务端返回拼装好的祝福字符串。完整版接口文件还额外声明了wishWithInfowishWithProviderwishWithErasedProviderwishFromFile等演示"更复杂 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!")) } }

实现要点:

  • 必须同时实现两个 traitbinder::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>之类的字段来统计调用次数;
  • 具体采用哪种并发方案(MutexRwLock或原子类型)取决于服务的状态访问模式,这是服务端设计时需要根据业务自行权衡的部分。

服务端二进制(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++ 等其他语言的用法相比可能更显繁琐,需要理解每一步的动机:

  1. 创建服务实例let birthday_service = BirthdayService;
  2. 把服务对象包装进生成的Bn*类型BnBirthdayService::new_binder(birthday_service, BinderFeatures::default())。这个BnBirthdayService由 Binder 编译器生成,提供通用的 Binder 功能,类似于 C++ 中的BnBinder基类。由于Rust 没有继承机制,这里用组合(composition)替代继承:把BirthdayService塞进生成的BnBirthdayService中;
  3. 调用binder::add_service:传入服务标识符(这里为字符串常量"birthdayservice")和服务对象(即包装后的BnBirthdayService),完成向系统服务管理器的注册,注册失败时.expect("Failed to register service")直接终止进程;
  4. 调用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_librarylibbirthdayservice),与rust_binarybirthday_server)分离,职责清晰;
  • 两者都依赖 AIDL 生成的 Rust cratecom.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 字符串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.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(()) }

客户端流程分四步:

  1. 解析命令行参数:从argv[1]读取名字(缺省Bob)、从argv[2]读取年龄(缺省42),演示了 Rust 标准库的参数处理惯用法;
  2. binder::ProcessState::start_thread_pool():客户端进程也需要启动 Binder 线程池,以接收来自 Binder 驱动的回调;
  3. binder::get_interface::<dyn IBirthdayService>(SERVICE_IDENTIFIER):按服务标识符向系统服务管理器查询并建立连接,返回Strong<dyn IBirthdayService>trait 对象;连接失败时通过map_err转成错误信息;
  4. 发起调用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]),只读借用;
  • outinout参数 →&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 路径中IBirthdayInfoProviderBnBirthdayInfoProvider成对出现(客户端侧还有对应的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、SpIBinderParcelFileDescriptor结构化数据、回调、类型擦除、跨进程文件传递

这门课程还配套了 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 16:08:59

YOLOv8破损绝缘子检测:数据集构建与训练优化全攻略

简介&#xff1a;面向电力智能巡检、无人机巡检与目标检测算法研究人群&#xff0c;这份数据集以真实场景标注图像为核心&#xff0c;用于解决绝缘子破损缺陷的识别与定位问题。包内共收录1801个文件&#xff0c;由600张JPG原图、600个XML标注和601个TXT标签组成&#xff0c;压…

作者头像 李华
网站建设 2026/9/10 16:08:49

Grasscutter资源包部署与避坑全指南:下载、安装、更新一次讲清

Grasscutter资源包部署与避坑全指南&#xff1a;下载、安装、更新一次讲清 【免费下载链接】Grasscutter A server software reimplementation for a certain anime game. 项目地址: https://gitcode.com/GitHub_Trending/gr/Grasscutter 服务器启动后资源加载失败、场景…

作者头像 李华
网站建设 2026/9/10 16:07:55

CANN/ge ATC工具环境设置

准备环境 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前端的…

作者头像 李华
网站建设 2026/9/10 16:06:56

【JAVA毕业设计】基于 Vue+SpringBoot 技术架构的证券模拟交易教学系统的设计与实现(源码+文档+远程调试,全bao定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/9/10 16:05:36

vue2-elementUI-初始化启动项目-git

前置基础 资料下载-阿里云盘 vueaxioselement-uinpmvscode 初始化项目 1.创建vue2工程 1.1 vue create projectName1.2 选择 1.3 初始化 vue-cli 的核心步骤&#xff1a; Manually select features (*) Babel ( ) TypeScript ( ) Progressive Web App (PWA) Support …

作者头像 李华