【免费下载链接】monogram
Native Telegram client for Android
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):
buildNativeMtproto:并行执行cargo ndk -t arm64-v8a -o jniLibs build --release等命令,为armeabi-v7a、arm64-v8a、x86_64三个 ABI 各编译一份.so;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 / UniFFI | Kotlin |
|---|---|
u64 | ULong |
i64 | Long |
String | String |
Vec<u8> | ByteArray |
uniffi::Record | data class |
uniffi::Enum | sealed 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 的扩展清单):
- 在对应的 owning 模块(
client/、media/、auth/)实现逻辑; - 在
ffi.rs中用#[uniffi::export]导出,参数用u64句柄,出入参用dto.rs里的 DTO; - 错误统一映射到
MtprotoError; - 重新生成绑定并更新 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
相关推荐
推荐开源项目:UniFFI - 多语言绑定的Rust利器
推荐开源项目:UniFFI 多语言绑定的Rust利器 在软件开发的世界里,跨语言交互是一项常见的挑战,而UniFFI正是为此而生的一款强大工具。它是一个用于构建
开发工具代码生成跨平台FFIMTProto协议深度剖析:Monogram用Rust实现的Telegram加密协议全链路
MTProto协议深度剖析:Monogram用Rust实现的Telegram加密协议全链路 Monogram 是一款纯原生的 Android 版 Telegra
Windows驱动签名强制绕过终极指南:DSEFix完整教程
Windows驱动签名强制绕过终极指南:DSEFix完整教程 你是否在Windows系统上安装自定义驱动程序时,被"驱动程序签名强制"的红色警告挡住去路?🤔
网络安全应用安全逆向工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考