Rust + Flutter 高性能的跨端尝试
source link: https://zhuanlan.zhihu.com/p/108308284
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Rust + Flutter 高性能的跨端尝试
稍作配置,同一份代码横跨 Android & IOS,相比于 React Native 方案更加高性能。除此之外,得益于 Rust 跨平台加持,Rust 部分的代码可在种种场合复用。
这篇文章旨在记录作者尝试结合 Rust 和 Flutter 的过程,且仅为初步尝试。不会涉及诸如:
如何搭建一个 Flutter 开发环境,以及 Dart 语言怎么用
如何搭建一个 Rust 开发环境,以及 Rust 语言怎么学
开头给自己博客打波广告
Environment
- Flutter: Android, IOS 工具配置妥当
- Rust: Stable 就好
Rust Part
Prepare cross-platform toolchains & deps
# Download targets for IOS ( 64 bit targets (real device & simulator) )
rustup target add aarch64-apple-ios x86_64-apple-ios
# Install cargo-lipo to generate the iOS universal library
cargo install cargo-lipo
Android
这里有一些行之有效的辅助脚本用于更加快捷配置交叉编译工具。
- 获取 Android NDK
sh sdkmanager --verbose ndk-bundle
如果已经准备好了 Android NDK ,则设置环境变量$ANDROID_NDK_HOME
```sh
example:
export ANDROID_NDK_HOME=/Users/yinsiwei/Downloads/android-ndk-r20b2. Create the standalone NDK
sh
$(pwd) == ~/Downloads
git clone https://github.com/kennytm/rust-ios-android.git cd rust-ios-android ./create-ndk-standalone.sh3. 在 Cargo default config VS 配置 Android 交叉编译工具
sh cat cargo-config.toml >> ~/.cargo/config执行上述命令后会在 Cargo 默认配置中,增加有关 Android 跨平台目标 (targets, `aarch64-linux-android`, `armv7-linux-androideabi`, `i686-linux-android`) 的工具信息,指向刚刚创建的 `standalone NDK`。
ini [target.aarch64-linux-android] ar = ... linker = ..
[target.armv7-linux-androideabi] ...
[target.i686-linux-android] ..4. 下载 Rust 支持 Android 交叉编译的依赖
sh rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android ```
Start a simple rust library
- 创建一个 Rust 项目
sh cargo init my-app-base --lib
- 编辑
Cargo.toml
修改crate-type
ini [lib] name = "my_app_base" crate-type = ["staticlib", "cdylib"]
Rust 构建出来的二进制库,在 IOS 中是静态链接进最终的程序之中,需要对构建staticlib
的支持;在 Android 是通过动态链接在运行时装在进程序运行空间的,需要对构建cdylib
的支持。 - 写一些符合 C ABI 的函数
src/lib.rs
```rust use std::os::raw::c_char; use std::ffi::CString;
[no_mangle]
pub unsafe extern fn hello() -> *const c_char { let s = CString::new("world").unwrap(); s.into_raw() } ```
在上述代码中,每次当外部调用hello
函数时,会在晋城堆空间中创建一个字符串 (CString
),并将所有权 ( 释放该字符串所占堆空间的权利 ) 移交给调用者。
Build libraries
# IOS
cargo lipo --release
# Android
cargo build --target aarch64-linux-android --release
cargo build --target armv7-linux-androideabi --release
cargo build --target i686-linux-android --release
然后在 target
目录下会得到以下有用的物料。
target
├── aarch64-linux-android
│ └── release
│ ├── libmy_app_base.a
│ └── libmy_app_base.so
├── armv7-linux-androideabi
│ └── release
│ ├── libmy_app_base.a
│ └── libmy_app_base.so
├── i686-linux-android
│ └── release
│ ├── libmy_app_base.a
│ └── libmy_app_base.so
├── universal
│ └── release
│ └── libmy_app_base.a
至此, Rust
部分就告于段落了。
Flutter Part
Copy build artifacts to flutter project
from: target/universal/release/libmy_app_base.a
to: ios/
from: target/aarch64-linux-android/release/libmy_app_base.so
to: android/app/src/main/jniLibs/arm64-v8a/
from: target/armv7-linux-androideabi/release/libmy_app_base.so
to: android/app/src/main/jniLibs/armeabi-v7a/
from: target/i686-linux-android/release/libmy_app_base.so
to: android/app/src/main/jniLibs/x86/
Call FFI function in Dart
- 添加依赖
pubspec.yaml
->dev_dependencies:
+=ffi: ^0.1.3
- 添加代码
(直接在生成的项目上修改,暂不考虑代码设计问题,就简简单单的先把项目跑起来 ) ```dart import 'dart:ffi'; import 'package:ffi/ffi.dart';
// ... final dylib = Platform.isAndroid ? DynamicLibrary.open('libmy_app_base.so') :DynamicLibrary.process(); var hello = dylib.lookupFunction Function(),Pointer Function()>('hello');
// ... hello(); // -> world ```
Build Android Project
flutter run # 如果连接着 Android 设备就直接运行了起来
Build IOS Project
( 复杂了许多 )
- 跟随
Flutter
官方文档,配置XCode
项目。 - 在
Build Phases
中Link Binary With Libraries
添加libmy_app_base.a
文件 (按照图上箭头点...)
- 在
Build Settings
中Other Linker Flags
中添加force_load
的参数。
这是由于在 Dart 中通过动态的方式调用了该库的相关函数,但在编译期间静态分析的时候,这些都是未曾被调用过的无用函数,就被剪裁掉了。要通过 force_load
方式解决这个问题。
Result
Troubleshooting
XCode & IOS
Error getting attached iOS device: ideviceinfo could not find device
sudo xattr -d com.apple.quarantine ~/flutter/bin/cache/artifacts/libimobiledevice/ideviceinfo
将后面的路径替换成你的
dyld: Library not loaded
dyld: Library not loaded: /b/s/w/ir/k/homebrew/Cellar/libimobiledevice-flutter/HEAD-398c120_3/lib/libimobiledevice.6.dylib
Referenced from: /Users/hey/flutter/bin/cache/artifacts/libimobiledevice/idevice_id
Reason: image not found
删除&重新下载
rm -rf /Users/hey/flutter/bin/cache && flutter doctor -v
真机无法启动 Flutter 程序
参见 https://github.com/flutter/flutter/issues/49504#issuecomment-581554697 不要升级到 IOS 13.3.1 系统
What's next
- 如何高效的实现 Rust & Dart 部分的通信
我们知道 Flutter 和广大 GUI 库类似,属于单线程模型结合事件系统,因此在主线程中使用 FFI 调用 Rust 部分的代码不能阻塞线程。Dart 语言提供 async/await 语法特性用于在 Flutter 中处理网络请求等阻塞任务。而 Rust 也在最近版本中提供了 async/await 语法支持,如何优雅的把两部分结合起来,这是一个问题。 - 对 MacOS Windows Linux 桌面端的支持
Flutter 已经有了对桌面端的实验性支持,可以研究下如何结合在一起,实现跨 6 个端共享代码。
References
介绍了如何构建出 Android, IOS 库,并提供了例子 - https://github.com/TimNN/cargo-lipo
用于构建 universal library - https://github.com/hanabi1224/flutter_native_extensions
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK