UniFFI 跨平台开发:Rust 与 Android (Kotlin) 集成实战教程
本文将指导你如何使用 UniFFI 自动化生成 Rust 与 Kotlin 的胶水层代码,省去繁琐的手写 JNI 过程。
第一阶段 环境准备
1.1 编译的环境
ubuntu 操作系统(Linux操作系统)
1、开发工具 RustRover
2、安装NDK环境。
mkdir -p ~/lib/android-ndk && cd ~/lib/android-ndk
wget https://dl.google.com/android/repository/android-ndk-r26b-linux.zip
unzip android-ndk-r26b-linux.zip
windows 操作系统
1、开发工具 AndroidStudio
1.2 结构图
效果图

1.3 生活案例
可以用 "一家跨国公司(Android)聘请一位只会讲家乡话的外国专家(Rust)来干活,并安排一位全能翻译(UniFFI)" 的故事来完美解释
第一阶段:外国专家入职(Rust 项目区)
- 故事背景: 你的公司(Android 应用)需要解决一个超级复杂的数学难题(比如高效的图像处理)。你听说有个叫 Rust 的外国专家特别厉害,不仅干活快(高性能),还从来不犯错(内存安全)。
- src/lib.rs (业务逻辑) = 专家的核心技能: 专家在他的办公室(Rust 环境)里,用他的家乡话(Rust 语言)写好了解决难题的数学公式(代码)。
- UniFFI setup_scaffolding! (自动生成脚手架) = 专家安装办公桌: 专家为了能接收工作和传递结果,需要在办公室里安装一张特制的办公桌。这个办公桌是全自动的,能把东西整齐地摆放好,不需要专家自己操心。
- CARGO 编译 (cargo-ndk build) = 专家打包行李和工具: 专家要把他的技能和办公桌打包,准备去你的公司。因为你的公司在不同的地方(真机 arm64-v8a 或模拟器 x86_64),专家必须把行李打包成能在不同地方打开的样式(
.so动态库)。
第二阶段:全能翻译官的准备(中间产物与生成工具)
- 故事背景: 专家虽然厉害,但他只说家乡话(Rust),你的员工(Kotlin)一句也听不懂。你需要一个中间人。
- uniffi-bindgen.rs (生成器工具) = 招聘全能翻译官: 你雇佣了一个叫 UniFFI 的翻译官。
- KOTLIN 胶水代码生成 = 翻译官编写工作手册: 翻译官 UniFFI 非常聪明。他先去专家的办公室看了一眼,搞清楚了专家能干什么活。然后,翻译官用你的员工能听懂的语言(Kotlin),写了一本详细的**《工作调用手册》**(
asrust.kt)。这本手册告诉你的员工,如果想找专家干活,该怎么说、传递什么参数。 .so动态库 +asrust.kt= 专家的行李 + 工作手册: 这时候,专家的行李(能在 Android 上运行的核心逻辑)和翻译官写的《工作调用手册》(asrust.kt)都准备好了。
第三阶段:入驻公司(Android 集成区)
- 故事背景: 你把专家和翻译官都请进了公司。
- asrust.kt 入驻
src/main/java/= 手册放到员工办公桌: 你把《工作调用手册》发给了负责具体业务的员工(MainActivity.kt)。 .so库入驻src/main/jniLibs/= 行李搬进公司仓库: 你把专家的行李放进了公司的专属仓库(jniLibs)。公司会根据当前的地址(真机还是模拟器),自动打开对应仓库里的行李。- build.gradle (依赖配置) = 购买翻译设备(JNA): 翻译官在工作时,需要一套专业的无线对讲机和耳机(JNA 库)。你在公司的采购清单(
build.gradle)里加上了这套设备。
第四阶段:正式开工(运行时调用流)
- 故事背景: 准备就绪,客户提出了请求。
- 1. 调用 Kotlin 函数 (MainActivity.kt -> asrust.kt) = 员工按手册下单: 客户让你的员工计算一个题。员工翻开桌上的《工作调用手册》(
asrust.kt),拨通了手册上的内部电话。员工用普通的 Kotlin 语言说:"我要计算这个题,参数是 A 和 B。" - 2. JNA 动态加载 (asrust.kt -> JNA -> libasrust.so) = 翻译官拿起设备打电话: 翻译官(
asrust.kt)接到电话后,立刻戴上对讲机(JNA 运行时),按下按钮,对讲机连接到了仓库里的专家行李(libasrust.so)。 - 3. 执行 Rust 逻辑 (libasrust.so -> Rust 方法) = 对讲机那头专家干活: 在对讲机的另一头,专家的行李自动激活了核心逻辑。专家接收到了翻译官传来的参数,飞快地用他的家乡话(Rust)把题算完了。
- 4. 返回结果 (专家 -> 翻译官 -> 员工) = 专家回复,翻译官传达: 专家算完后,对着对讲机说:"结果是 C。"翻译官听完后,摘下耳机,用回 Kotlin 语言对你的员工说:"专家算出来了,结果是 C。"
你的员工(MainActivity.kt)拿到了结果 C,开心地展示给了客户。而在这个过程中,你的员工从头到尾都不需要知道专家住在哪个仓库(JNI 细节),也不需要学会一句 Rust 方言,一切都由翻译官(UniFFI)和翻译设备(JNA)在背后默默处理好了。
第二阶段:Rust 项目配置
2.1 Cargo.toml 配置
在 Cargo.toml 中,关键点是定义 crate-type 为动态库,并引入 UniFFI 依赖。
Ini, TOML
toml
[package]
name = "asrust"
version = "0.1.0"
edition = "2021"
[lib]
name = "asrust"
crate-type = ["cdylib", "staticlib"] # 必须包含 cdylib 以生成 .so 库
[dependencies]
uniffi = { version = "0.31.1", features = ["cli"] }
[build-dependencies]
uniffi = { version = "0.31.1", features = ["build"] }
# 优化 Android 编译链接参数,解决某些设备上的符号链接问题
[target.aarch64-linux-android]
rustflags = ["-C", "link-arg=-lgcc", "-C", "link-arg=-static-libgcc"]
[target.x86_64-linux-android]
rustflags = ["-C", "link-arg=-lgcc", "-C", "link-arg=-static-libgcc"]
2.2 编写业务逻辑 (src/lib.rs)
使用 setup_scaffolding! 宏是 UniFFI 的核心,它会自动处理底层的 FFI 转换。
rust
// 自动生成底层的脚手架代码
uniffi::setup_scaffolding!();
#[uniffi::export]
pub fn hello_world(a: i32, b: i32) -> i32 {
a + b
}
2.3 定义生成器入口 (src/bin/uniffi-bindgen.rs)
为了方便在项目内部生成 Kotlin 代码,我们需要这个二进制入口。
rust
fn main() {
uniffi::uniffi_bindgen_main()
}
第三阶段:编译与代码生成
3.1 环境准备 (NDK)
确保你的环境变量中包含 ANDROID_NDK_HOME。
shell
# 下载并解压 NDK (以 r26b 为例)
mkdir -p ~/lib/android-ndk && cd ~/lib/android-ndk
wget https://dl.google.com/android/repository/android-ndk-r26b-linux.zip
unzip android-ndk-r26b-linux.zip
3.2 编译 Rust 库
使用 cargo-ndk 针对指定架构进行编译。
shell
# 安装工具
cargo install cargo-ndk
# 编译 arm64 架构(真机常用)或 x86_64(模拟器)
cargo ndk -t arm64-v8a -t x86_64 build --release
3.3 生成 Kotlin 胶水层
运行生成的二进制工具,根据编译出的 .so 文件提取接口并生成 Kotlin 代码。
注意这里的参数
1、输入路径 --library
2、输出路径 --out-dir
两部分内容:
shell
cargo run --bin uniffi-bindgen generate \
--library ./target/x86_64-linux-android/release/libasrust.so \
--language kotlin \
--out-dir ./src
第四阶段:Android 集成
4.1 引入依赖与配置
UniFFI 生成的代码依赖于 JNA (Java Native Access)。
build.gradle (Module level):
groovy
dependencies {
// UniFFI 运行时必须依赖 JNA
implementation("net.java.dev.jna:jna:5.18.1@aar")
}
4.2 放置二进制库
将生成的 .so 文件放入对应的目录。注意目录名必须与 Android 架构对应。
Plaintext
app/src/main/jniLibs/
├── arm64-v8a/
│ └── libasrust.so
└── x86_64/
└── libasrust.so
4.3 导入生成的代码
将生成的 asrust.kt 文件拷贝到你的项目包名目录下(例如 com.example.app)。
注意: 你不需要手动修改
asrust.kt里的代码。UniFFI 会自动为你封装好FfiConverter等复杂逻辑。
4.4 最终调用
在 Kotlin 中,你不再需要编写 external 方法,直接像调用普通函数一样使用:
Kotlin
import com.example.app.helloWorld
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 直接调用,UniFFI 在后台处理了所有复杂的 JNI 转换
val sum = helloWorld(10, 20)
println("Result from Rust: $sum")
}
}