因为rust.cc不支持一些github支持的markdown语法,想要有更好的体验,可以跳到这里
https://github.com/s1rius/ezlog/blob/master/docs/JOURNAL.md
在过去的几周里,我根据 Xlog 和 Loagan 的设计思路,使用 Rust 写了一个移动端的跨平台日志库EZLog。在我实现这个库的过程中,查阅了大量的问答和博客。因为这些开发者的分享,节省了我大量的时间。所以我把我的经历也分享出来。
如果以下任何一个点你感兴趣,不要划走。
- 当下(2022)Rust 在移动端的开发体验如何
- 没有 Java/Kotlin 代码,不使用 AndroidStudio 和 Gradle, 秒级编译并打包 Apk 运行
- Rust 编译静态库打包到 XCFramework, 发布 Cocoapods
起因
22 年初,我有一个游戏需求,要在 android 上实现匀速的贝塞尔曲线路径移动。依赖 kurbo写了一个生成贝塞尔曲线 LUT 的命令行工具,体验很好。于是想尝试一下,发挥 Rust 的优势,在移动端写一个对性能有要求的开源库,第一个想到的就是日志。
可行性调查
大型公司在移动端使用 Rust
- Google 在 2021 年将 Rust 引入 Android Rust in the Android platform
- Mozilla 使用 Rust 编写跨平台应用服务组件Firefox Application Services
- 飞书客户端非 UI 部分使用 Rust 跨平台实现
个人开发者在移动端的尝试 Rust 的案例
- Rust & cross-platform mobile development
- RustDesk 远程桌面应用
- 深度探索:前端中的后端
- Publish game on Android with Macroquad
- Rust on iOS and Mac Catalyst: A Simple, Updated Guide
更多的案例收录,可以参看这篇Rust 移动开发与跨平台模式探究。从以上的例子来看,大概率是可行的,还要对具体的需求进行验证。
项目简述
主要功能
- 使用 mmap 做文件映射
- 认证加密
- zlib 压缩
- 日志回捞
- 日志清理
- 命令行解析工具
可行性验证
先查找主要功能是否有对应的 Rust 开源实现。
- memmap2
- aead
- flate2
开源社区早已实现好了:) 复制开源库中的示例,借助android-ndk-rs。在 android 真机运行,查看日志输出,符合预期。项目中使用android ndk rs
的例子,可以查看examples/android_preview
cargo apk run -p ezlog_android_preview
Compiling ezlog v0.1.2 (ezlog/ezlog-core)
Compiling ezlog_android_preview v0.1.0 (ezlog/examples/android_preview)
Finished dev [unoptimized debuginfo] target(s) in 2.39s
'lib/arm64-v8a/libezlog_android_preview.so'...
Verifying alignment of ezlog/target/debug/apk/ezlog_android_preview.apk (4)...
49 AndroidManifest.xml (OK - compressed)
1020 lib/arm64-v8a/libezlog_android_preview.so (OK)
Verification succesful
Performing Incremental InstallServing...
All files should be loaded. Notifying the device.SuccessInstall command complete in 949 msStarting: Intent { act=android.intent.action.MAIN cmp=rust.ezlog_android_preview/android.app.NativeActivity }
项目结构
代码语言:javascript复制├── android
│ ├── app # android 示例工程│ └── lib-ezlog # EZLog android 库├── examples # rust 示例├── ezlog-cli # 命令行工具├── ezlog-core # 核心库├── ios
│ ├── EZLog # EZLog iOS 库│ ├── demo # iOS 示例工程│ └── framework # EZLog XCFramework
开发中碰到的问题及解决
iOS
iOS 端的开发流程为
- Rust 编码
- 通过 cbindgen 生成头文件
- 编译多平台静态库
- 把静态库和头文件打包成 XCFramework,并依赖
- 实现 Swift 绑定
- 测试,发布
在对比了多种依赖静态库的方式之后,发现XCFramework对多平台的支持,更适合这个项目。更多 XCFramework 的相关资料可以看这几篇文章distributing universal ios frameworks as xcframeworks using cocoapods, Static libraries into XCFramework,From Rust To Swift。在项目中的构建使用,可以参看ios/b_ios.sh
脚本。
Swift 与 C 的互相调用,很多概念需要了解。在被Unmanaged
, @escaping
,@convention
,UnsafePointer
,UnsafeBufferPointer
, UnsafeMutableRawPointer
折磨许久之后,终于可以在 Swift 中拿到 Rust 的回调了。
Cocoapods 支持 XCFramework,尝试了 SPM,找不到符号的问题没有解决。暂时放一放。在花费了以天计的时间成本之后,终于在 Cocoapods 成功发布。
对比一下三个包管理工具的从注册到发布的时间成本,从简单到繁琐的排序是 Cargo < Cocoapods < Maven。
随着苹果在 XCode14 中废弃了 bitcode,Rust 在 iOS/MacOS 中最大的痛点也就消失了。
android FFI
android 上 Rust 与 JNI 的互调和 C/C 的区别不大。同样要考虑变量的生命周期,全局的 JavaVM 引用,类加载到 JVM,native 线程在 JVM 的 attach 和 detach,详细的示例代码可以查看jni-rs。
得益于 Rust 的生命周期管理,一些内存清理操作 Rust 已经处理了,不需要我们再手动的处理。
代码语言:javascript复制impl<'a: 'b, 'b> Drop for JavaStr<'a, 'b> { fn drop(&mut self) { match self.env.release_string_utf_chars(self.obj, self.internal) { Ok(()) => {} Err(e) => warn!("error dropping java str: {}", e),
}
}
}
android 动态库大小
因为用户对 RAM,流量的关心和 Android 版本向前兼容原因,android 开发中对包体积大小是敏感的。对于 Rust 编译产物体积较大的问题,在查阅了Minimizing Rust Binary Size文章后,在 release 模式开启优化。
我们将 XLog,Logan 都加入到 Demo 的依赖中,对比 release apk 中的 64 位动态库大小,如图所示
EZLog(0.1.2) | EZLog(with backtrace) | XLog(1.2.5) | XLog(with libc ) | Logan(1.2.4) | |
---|---|---|---|---|---|
so 大小 | 370.6KB | 451.6KB | 277.1KB | 537KB | 27.5KB |
Rust 编译的动态库大小是最大的,不过也没有太过于夸张,随着手机硬件的进一步提升,应该不会是制约 Rust 在 android 中应用的原因。
崩溃?
不同的情况下,需要不同的方式
- 在开发,测试阶段,由于编写错误或者代码混淆等问题,导致类,方法无法找到的情况,我们希望程序能直接崩溃,尽早的暴露问题。
- 产品上线后,因为适配问题或者用户操作导致 bug 出现,导致某个模块的不可用。我们希望错误只限定在这个模块,不影响其他的功能,并上报这个错误
Rust 的错误分为可恢复和不可恢复的错误。Rust 初始化线程的 panic 会导致进程的退出。一些解决方法:
- 只在需要崩溃时使用 panic 宏
- 在 Clippy 中加入使用 unwrap 和 except 的警告
- 替换 [start..end] 为 get(start..end)
- FFI 中 catch_unwind
即使自己的代码中没有 panic 调用,依赖库中也可能会调用。所以需要提供在生产环境中崩溃排查的能力。
崩溃排查
初始化时设置 panic hook。
代码语言:javascript复制#[cfg(not(feature = "backtrace"))]fn hook_panic() {
std::panic::set_hook(Box::new(|p| {
event!(panic & format!("ezlog: n {p:?}"));
}));
}
在崩溃时会回调拿到 PanicInfo
代码语言:javascript复制PanicInfo { payload: Any { .. }, message: Some(asdf), location: Location { file: "ezlog-core/src/lib.rs", line: 119, col: 5 },
PanicInfo 中有错误信息,panic 的文件路径和代码位置。这样能粗略的排查 bug。如果想拿到具体的堆栈信息,我们还需要依赖 backtrace,这样最后动态库的大小会增加 80KB 左右
代码语言:javascript复制#[cfg(feature = "backtrace")]fn hook_panic() {
std::panic::set_hook(Box::new(|p| { let bt = Backtrace::new();
event!(panic & format!("ezlog: n {p:?} n{bt:?} n"));
}));
}
代码语言:javascript复制PanicInfo { payload: Any { .. }, message: Some(asdf), location: Location { file: "ezlog-core/src/lib.rs", line: 119, col: 5 }, can_unwind: true } 0: backtrace::backtrace::trace_unsynchronized 1: backtrace::backtrace::trace 2: backtrace::capture::Backtrace::create 3: backtrace::capture::Backtrace::new 4: ezlog::init::{{closure}} 5: std::panicking::rust_panic_with_hook 6: std::panicking::begin_panic_handler::{{closure}} 7: std::sys_common::backtrace::__rust_end_short_backtrace 8: _rust_begin_unwind 9: core::panicking::panic_fmt 10: ezlog::init 11: _ezlog_init 12: _$s5EZLog18ezlogInitWithTraceyyF 13: _$s4demo7DemoAppVACycfC 14: _$s4demo7DemoAppV7SwiftUI0C0AadEPxycfCTW 15: <unknown> 16: _$s4demo7DemoAppV5$mainyyFZ 17: _main
代码语言:javascript复制PanicInfo { payload: Any { .. }, message: Some(asdf), location: Location { file: "ezlog-core/src/lib.rs", line: 119, col: 5 }, can_unwind: true }
0: <unknown>
1: <unknown>
2: <unknown>
3: <unknown>
4: <unknown>
5: <unknown>
6: <unknown>
7: Java_wtf_s1_ezlog_EZLog_init
8: art_quick_generic_jni_trampoline
9: art_quick_invoke_static_stub10: _ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_11ShadowFrameEtPNS_6JValueE11: _ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE12: MterpInvokeStatic13: mterp_op_invoke_static14: _ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEbb.llvm.335106805463763666415: _ZN3art11interpreter33ArtInterpreterToInterpreterBridgeEPNS_6ThreadERKNS_20CodeItemDataAccessorEPNS_11ShadowFrameEPNS_6JValueE16: _ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE17: MterpInvokeStatic18: mterp_op_invoke_static19: _ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEbb.llvm.335106805463763666420: _ZN3art11interpreter33ArtInterpreterToInterpreterBridgeEPNS_6ThreadERKNS_20CodeItemDataAccessorEPNS_11ShadowFrameEPNS_6JValueE
代码语言:javascript复制/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释
* 2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: Build fingerprint: 'google/flame/flame:12/SP2A.220305.012/8177914:user/release-keys'2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: Revision: 'MP1.0'2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: ABI: 'arm64'2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: Timestamp: 2022-07-07 15:25:50.559643355 08002022-07-07 15:25:50.712 14141-14141/? A/DEBUG: Process uptime: 0s2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: Cmdline: wtf.s1.ezlog.demo2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: pid: 14112, tid: 14112, name: f.s1.ezlog.demo >>> wtf.s1.ezlog.demo <<<2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: uid: 102872022-07-07 15:25:50.712 14141-14141/? A/DEBUG: signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x0 0000000000000000 x1 0000000000003720 x2 0000000000000006 x3 0000007fc9d511202022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x4 00000000ebad808a x5 00000000ebad808a x6 00000000ebad808a x7 00000000ebad808b2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x8 00000000000000f0 x9 f7d7529acf61ddf5 x10 0000000000000000 x11 ffffff80fffffbdf2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x12 0000000000000001 x13 0000007fc9d50fe8 x14 0000000000000000 x15 00000000000000082022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x16 0000007b425b6050 x17 0000007b42592db0 x18 0000007b523fa000 x19 00000000000037202022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x20 0000000000003720 x21 00000000ffffffff x22 0000000000000001 x23 00000000000000012022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x24 000000782576bad8 x25 00000078256bb708 x26 000000000000000b x27 0000007b515530002022-07-07 15:25:50.712 14141-14141/? A/DEBUG: x28 0000007fc9d514a0 x29 0000007fc9d511a02022-07-07 15:25:50.712 14141-14141/? A/DEBUG: lr 0000007b42545aa0 sp 0000007fc9d51100 pc 0000007b42545acc pst 00000000000000002022-07-07 15:25:50.712 14141-14141/? A/DEBUG: backtrace:2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #00 pc 000000000004facc /apex/com.android.runtime/lib64/bionic/libc.so (abort 164) (BuildId: cd7952cb40d1a2deca6420c2da7910be)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #01 pc 00000000000b3f1c /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #02 pc 00000000000b22a8 /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #03 pc 00000000000b2164 /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #04 pc 00000000000b203c /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #05 pc 00000000000b142c /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #06 pc 00000000000b1e6c /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #07 pc 00000000000c5ad0 /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #08 pc 000000000007550c /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #09 pc 00000000000741ec /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/lib/arm64/libezlog.so (Java_wtf_s1_ezlog_EZLog_init 28)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #10 pc 00000000002d4044 /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline 148) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #11 pc 00000000002ca9e8 /apex/com.android.art/lib64/libart.so (art_quick_invoke_static_stub 568) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #12 pc 00000000002ee6b8 /apex/com.android.art/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*) 320) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #13 pc 000000000040ade4 /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 820) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #14 pc 000000000076d4b8 /apex/com.android.art/lib64/libart.so (MterpInvokeStatic 3812) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #15 pc 00000000002c5014 /apex/com.android.art/lib64/libart.so (mterp_op_invoke_static 20) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #16 pc 00000000000790a2 [anon:dalvik-classes.dex extracted in memory from /data/app/~~eRgxj9PHRfJEC-cex2WWJw==/wtf.s1.ezlog.demo-ngUhJZd2NWtJpUNIWy5f_g==/base.apk] (wtf.s1.ezlog.EZLog.initWith 10)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #17 pc 000000000027d840 /apex/com.android.art/lib64/libart.so (art::interpreter::Execute(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame&, art::JValue, bool, bool) (.llvm.3351068054637636664) 644) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #18 pc 000000000035a9e4 /apex/com.android.art/lib64/libart.so (art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame*, art::JValue*) 148) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #19 pc 000000000040b05c /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*) 1452) (BuildId: 46df93bc978921840e5b428398c66a57)2022-07-07 15:25:50.712 14141-14141/? A/DEBUG: #20 pc 000000000076d4b8 /apex/com.android.art/lib64/libart.so (MterpInvokeStatic 3812) (BuildId: 46df93bc978921840e5b428398c66a57)
*/
可以发现,android 的堆栈输出里并没有完整的 Rust 调用堆栈,我尝试通过add2line的方法,但没有成功。
使用 Rust 的体验
- 学习曲线陡峭
我在读完 Rust 官方文档 后,又跟着 Rust 第一步 敲了一遍代码。发现这只是个开始,在看了一遍Rust Nomicon Rust Nomicon 中文和Async Book之后,就已经想放弃了。太多晦涩的内容了,比如:Unsafe
, PhantomData
,Send and Sync
, Pin
...
直接一边写项目一边学吧。
在实际的项目中,开源社区提供的解决方案有时会更合适。比如:crossbeam_channel
, tokio
, once_cell
, thiserror
等等,在 crates.io 上看开源项目文档也是学习的一部分。
- 文档
Rust 大部分库的文档,对读者很友好。详尽的描述,完整的示例。和 Java 的 concurrent 包的文档读着一样舒服。看这样的文档,好像就坐在作者的大脑皮层上看他写代码。反观 iOS 和 Andriod 的一些文档,字里行间仿佛写着“你猜猜看这个怎么用”。
- 单元测试
易用的单元测试,随时添加#[test]
就可以写一个测试用例。对比在 android 项目写一个测试用例,我还要先去搜索,有哪些依赖是需要添加的。用 Rust 做开发,和系统 API 无关的业务逻辑,在桌面环境完成并测试,最后到对应的客户端验证。效率比起所有逻辑都在手机上验证高多了。
- 错误处理
Rust 可恢复错误强制处理,当我第一次看到 Result 那巨长的方法列表,就像看到大闸蟹身上一圈一圈的绳子,要不一剪刀(unwrap)了事?我们不想在库里直接 panic,就只能看文档了。避免图片过长,用了 3 张图显示:
习惯了 try catch 的错误处理方式,刚开始处理 Result/Option 是懵的。通过文档示例和 Clippy 提示,花一些时间就能掌握。当我开始熟悉这样的错误处理方式,我不时会怀疑,之前是怎么在 Java/Kotlin 中只用 try catch 就能写完那些代码的。。。
- 生命周期和所有权
Rust 编译器的所有权/生命周期检查,必须在编码的时候就考虑对象的生命周期和内存的分配问题。对比 C/C 将编码的痛苦前置了。对比有 GC 的语音,心智成本大幅提高。
- 编译时间
debug 模式下
代码语言:javascript复制cargo clean && cargo build -p ezlogFinished dev [unoptimized debuginfo] target(s) in 14.19s
使用缓存,更改一行代码,再次编译
代码语言:javascript复制cargo build -p ezlogFinished dev [unoptimized debuginfo] target(s) in 1.46s
无缓存 release 模式下
代码语言:javascript复制target `aarch64-apple-ios`
Finished release [optimized] target(s) in 45.43s
target `aarch64-apple-ios-sim`Finished release [optimized] target(s) in 40.83s
target `x86_64-apple-ios`Finished release [optimized] target(s) in 39.26s
Building armeabi-v7a (armv7-linux-androideabi)
Finished release [optimized] target(s) in 47.64s
Building arm64-v8a (aarch64-linux-android)
Finished release [optimized] target(s) in 50.71s
在小型项目使用 Rust,基本没有编译摸鱼时间。对于移动端开发来说,有过无编译优化的中型项目的 Gradle 或者 XCode 构建体验,这点编译时间都不算事。如果想要加速 Android 端验证的效率,那么最好单独新增 crate,用android-ndk-rs这样的工具,动态获取 target,因为没有 Java/Kotlin 代码,跳过 gradle 的构建直接生成 APK 部署。
总结
学习成本
对于新手,Rust 文档友好,社区活跃。还有官方支持的的包管理工具 Cargo,代码检查工具 Clippy。个人认为上手难度要低于 C 。
开发效率
Rust 的学习曲线陡峭,编译器对借用,生命周期的检查。导致新手在初期开发效率低下。随着对语言熟悉程度的提高,以及易用的测试框架,方便的跨平台编译,可以弥补前期的开发效率劣势,加上 Rust 内存安全的特点,也降低了后期项目维护的难度。
适用
从 0 开始构建一个新的跨平台 App,所有的非 UI 逻辑,都使用 Rust 实现,构建成单一的静态/动态库,提供 FFI 支持。Flutter/RN/Compose/SwiftUI 等等的框架做 UI 交互。在这种场景下使用 Rust 很合适。
但是如果想在现有的 App 中大量使用 Rust,那么二进制依赖是一个问题,如果有多个业务 crate,打包成多个二进制文件。那么各个 Rust 的产物可能会包含相同的依赖,比如 libc, syn, cfg-if, memchr等等。最后的包大小会随着业务的增多快速膨胀。
后续
项目需要继续完善的地方
- Unsafe 代码审查
- 提供详细事件监控回调
- 文档完善
- 示例完善
- 性能测试
- GUI 支持
除了实现了 android,iOS 的跨平台,还有一些其他的使用场景。
- 兼容 Windows,满足桌面端的需求。
- 实现 Dart 的 FFI,提供 Flutter SDK。
- 给其他的库,游戏框架提供 log 插件,比如:Bevy,Tauri。
Tips
- 添加 config,快速切换 target
在项目中添加 ./.cargo/config,并配置
代码语言:javascript复制[build]
target = "aarch64-linux-android"...
注释掉其他的 target 后,在 vscode 中双击 shift 输入 >> 找到 reload workspace,就可以切换到对应的 target 了。
- 使用 thiserror 库来处理自定义的错误类型,可以省掉很多模版代码。
参考文章及开源项目
- mozilla multi platform arch design
- Android Rust crates
- Android Rust Introduction
- Flutter Rust Bridge
FFI
- rust ffi doc
- how to call rust functions from c on linux h37
- how does rust ffi pass parameters of type vec u8
- How to return byte array from rust to c
android
- Rust bindings to the JNI
- JNI crate exapmles
- Implementing JNI_OnLoad
- Rust on Android
- cargo ndk
- Minimizing Rust Binary Size
- Rust 中的 bin, lib, rlib, a, so 概念介绍
iOS
- Create your own CocoaPods library
- Building and Deploying a Rust library on iOS via Mozilla
- Rust on iOS and Mac Catalyst
- recipe swift rust callback
其他
- Backtrace Capture on mobile in production enviroment
- Cannot get backtrace on Android
- Gimli doesn't support iOS
- Transform backtrace to string during catch unwind
- Android backtraces don't work since gimli is used as the symbolizer