☰
Kotlin与Rust如何协作?Monogram中UniFFI跨语言绑定MTProto库的完整实践
2026/10/23 4:31:04 网站建设 项目流程

【免费下载链接】monogram

Native Telegram client for Android

项目地址:https://gitcode.com/gh_mirrors/mo/monogram
点击查看免费下载

Monogram 是一款基于 Kotlin + Jetpack Compose 构建的 Android 原生 Telegram 客户端,它的核心亮点在于:网络协议层(MTProto)完全由 Rust 实现,并通过 Mozilla 出品的 UniFFI 工具完成跨语言绑定。本文以 Monogram 为实例,完整讲解 Kotlin 与 Rust 如何通过 UniFFI 协作,带你走通从 Rust 导出函数、自动生成 Kotlin 绑定,到 Gradle 构建集成的全流程。

为什么要用 Rust 写 MTProto?

MTProto 是 Telegram 私有通信协议,涉及大量的加解密、字节流编解码、连接状态机与并发调度,属于典型的"性能 + 安全性"敏感代码。Monogram 选择用 Rust 重写协议栈(见 native/mtproto-rs/README.md),带来三个好处:

  • 🚀性能:无 GC、贴近裸金属的执行效率,适合处理高频的网络包与媒体分片;
  • 🔒内存安全:协议错误、缓冲区越界等问题在编译期就被消除;
  • 📦可复用:同一套 Rust crate 可以生成 Kotlin、Swift、Python、Ruby 多语言绑定。

但 Android 上层 UI 仍然是 Kotlin。两者之间的"桥",就是本文的主角——UniFFI。

整体分层:一次调用的完整旅程

Monogram 对 Kotlin 和 Rust 的边界划分非常严格,调用链自上而下是:

feature/*(UI 功能模块,Kotlin) ↓ 只允许调用 network/bridge(Kotlin 客户端 API 封装) ↓ native/mtproto(Kotlin 门面层 + UniFFI 生成绑定) ↓ FFI(JNI / JNA) native/mtproto-rs(Rust 协议实现,libmonogram_mtproto.so) ↓ Telegram 数据中心

其中 network/bridge/src/main/java/org/monogram/network/bridge/BridgedMtprotoClient.kt 是 Kotlin 侧的总入口,负责把原生 DTO 映射成应用领域模型。项目约定:feature 模块禁止直接 import UniFFI 生成类型,所有原生调用必须经过 bridge 层,这条纪律保证了协议层未来重构时 UI 层零改动。

Rust 侧:用三种宏把 API 暴露出去

Rust crate 名为monogram-mtproto(见 native/mtproto-rs/Cargo.toml),crate-type声明为cdylib,即编译产物是一个可被 Android 加载的动态库libmonogram_mtproto.so。

UniFFI 的绑定模型非常直白,Rust 侧只需要写三类内容:

1️⃣ 用#[uniffi::export]导出函数

所有对外 API 集中在 native/mtproto-rs/src/ffi.rs,例如:

#[uniffi::export] pub fn get_wallpapers(handle: u64, hash: i64) -> Result<WallpaperCatalogDto, MtprotoError> { client::get_wallpapers(handle, hash) }

注意两点设计:客户端句柄是简单的u64整数(由createClient返回),而不是对象指针;每个导出函数都是同步阻塞的,由调用方决定在哪个线程执行。

2️⃣ 用uniffi::Record/uniffi::Enum定义跨边界的数据

跨 FFI 传输的数据结构统一定义在 native/mtproto-rs/src/dto.rs:

#[derive(Debug, Clone, uniffi::Record)] pub struct AuthCodeSent { pub phone: String, pub phone_code_hash: String, pub code_type: String, pub code_length: i32, }

Record在 Kotlin 侧会生成data class,Enum会生成 sealed class——类型系统是"一一对应"的,这也是 UniFFI 比手写 JNI 更省心的地方。

3️⃣ 用uniffi::Error定义错误类型

错误统一收敛为 native/mtproto-rs/src/error.rs 中的MtprotoError,跨边界后自动转成 Kotlin 异常抛出,无需手动解析错误码。

最后在 native/mtproto-rs/src/lib.rs 一行uniffi::setup_scaffolding!();宏,就完成了整个 crate 的 FFI 脚手架生成。

绑定如何生成?Gradle 里的两步自动化

UniFFI 支持"从编译后的 .so 反向提取 API 并生成绑定",Monogram 把这一步做进了 Gradle 任务(见 native/mtproto/build.gradle.kts):

  1. buildNativeMtproto:并行执行cargo ndk -t arm64-v8a -o jniLibs build --release等命令,为armeabi-v7a、arm64-v8a、x86_64三个 ABI 各编译一份.so;
  2. generateUniffiMtproto:先用宿主架构编译一个 debug 版 cdylib,再运行 native/mtproto-rs/uniffi-bindgen.rs 中的 bindgen 二进制:
uniffi-bindgen generate --library libmonogram_mtproto.so --language kotlin --out-dir src/main/java

产出的 Kotlin 文件被提交在仓库中:native/mtproto/src/main/java/uniffi/monogram_mtproto/monogram_mtproto.kt(约 8000 行)。它内部通过JNA(而非手写 JNI C 代码)来调用.so中的符号,文件头部的注释也写得很明白:"This file was autogenerated... you don't want to mess with it!"

💡 日常开发可以加-PskipNativeBuild=true跳过原生编译与绑定重生成,只做 Kotlin 侧改动时能大幅提速(见 README.md 的说明)。

Kotlin 侧:一套"接口 + 实现 + 加载器"的四件套

UniFFI 生成的代码是"裸"的,Monogram 在它外面再包了一层手写抽象(位于native/mtproto/src/main/java/org/monogram/mtproto/):

文件职责
MtprotoNative.kt纯 Kotlin 接口,定义connect、sendAuthCode、getChats等全部能力
MtprotoNativeUniFfi.kt实现类,逐一对接 UniFFI 生成的原生函数(如connect as nativeConnect)
MtprotoNativeLoader.kt加载器,负责初始化并缓存单例
MtprotoNativeStub.kt兜底 Stub,原生库不可用时显式让网络调用失败

这种"面向接口编程"带来两个实际收益:单测可以用 Stub 替代真实.so;真机加载失败时应用仍能启动,只是网络功能降级。

线程模型是另一个关键点:所有 UniFFI 导出函数都是同步阻塞的,直接在调用线程执行并阻塞到 Rust 侧返回。因此 Kotlin 侧必须把调用放到协程的 worker dispatcher 上执行,这正是 bridge 层的职责。此外,进度回调(如下载进度DownloadProgressListener)运行在 Rust 的原生下载线程上,跨边界的只有简单参数。

类型映射规则(摘自 native/mtproto-rs/ARCHITECTURE.md):

Rust / UniFFIKotlin
u64ULong
i64Long
StringString
Vec<u8>ByteArray
uniffi::Recorddata class
uniffi::Enumsealed class
Result<T, E>异常抛出

一个细节值得注意:媒体字节从不跨越 FFI 边界——下载由 Rust 直接写入目标文件路径,Kotlin 只拿到路径,避免了大文件在两个运行时间反复拷贝。

同一套模式:markup 模块复用

UniFFI 绑定在 Monogram 中不止服务 MTProto。native/markup-rs/src/lib.rs 里同样有一组uniffi::export导出,把 Markdown 解析、代码高亮、公式渲染交给 Rust,Kotlin 侧通过同一套生成流程获得绑定。这说明 UniFFI 方案在此项目中是一个可复制的工程模板:Rust 写核心算法 → 导出函数 → Gradle 任务生成绑定 → Kotlin 门面封装。

动手实践:编译与扩展指南

🔧从零构建:安装 JDK 17、Android NDK、Rust 1.98+ 与cargo-ndk,然后执行:

rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android cargo install cargo-ndk --locked ./gradlew :native:mtproto:buildNativeMtproto

🧩给库新增一个导出函数(摘自 ARCHITECTURE.md 的扩展清单):

  1. 在对应的 owning 模块(client/、media/、auth/)实现逻辑;
  2. 在ffi.rs中用#[uniffi::export]导出,参数用u64句柄,出入参用dto.rs里的 DTO;
  3. 错误统一映射到MtprotoError;
  4. 重新生成绑定并更新 Kotlin 侧MtprotoNative接口与 bridge 层。

生成的 Kotlin 绑定是"只重建、不手改"的产物,这是整个体系能长期可维护的纪律所在。

总结

Monogram 展示了 Kotlin 与 Rust 在 Android 上协作的完整范式:

  • Rust 负责协议:加密、调度、状态机、媒体传输全部收敛在native/mtproto-rs,边界小而稳定;
  • UniFFI 负责桥接:#[uniffi::export]+Record/Enum/Error三种宏定义 API,bindgen 从编译产物直接生成 Kotlin 绑定,杜绝手工同步;
  • Kotlin 负责宿主:接口化门面、协程调度、Stub 降级与 DTO 到领域模型的映射;
  • Gradle 负责自动化:cargo ndk交叉编译与绑定重生成都内嵌在preBuild依赖链里。

对想在自己的 Android 项目里引入 Rust 的同学来说,这套"Rust 核心 + UniFFI 绑定 + Kotlin 门面 + Gradle 集成"的组合,是一个可以直接照搬的成熟参考。

【免费下载链接】monogram

Native Telegram client for Android

项目地址:https://gitcode.com/gh_mirrors/mo/monogram
点击查看免费下载

相关推荐

上一篇:Apollo Save Tool:PS4存档管理的终极解决方案,新手也能轻松上手
下一篇:browsershot 安装与环境配置完整指南:Composer 集成、Puppeteer 运行环境搭建与自定义二进制路径

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询