Flutter + Rust 高性能的跨端尝试稍作配置,同一份代码横跨 Android & IOS,相比于 React
稍作配置,同一份代码横跨 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
IOS
# 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
sdkmanager --verbose ndk-bundle
如果已经准备好了 Android NDK ,则设置环境变量
$ANDROID_NDK_HOME
# example: export ANDROID_NDK_HOME=/Users/yinsiwei/Downloads/android-ndk-r20b
-
Create the standalone NDK
# $(pwd) == ~/Downloads git clone https://github.com/kennytm/rust-ios-android.git cd rust-ios-android ./create-ndk-standalone.sh
-
在 Cargo default config VS 配置 Android 交叉编译工具
cat cargo-config.toml >> ~/.cargo/config
执行上述命令后会在 Cargo 默认配置中,增加有关 Android 跨平台目标 (targets,
aarch64-linux-android
,armv7-linux-androideabi
,i686-linux-android
) 的工具信息,指向刚刚创建的standalone NDK
。[target.aarch64-linux-android] ar = ... linker = .. [target.armv7-linux-androideabi] ... [target.i686-linux-android] ..
-
下载 Rust 支持 Android 交叉编译的依赖
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android ```
Start a simple rust library
-
创建一个 Rust 项目
cargo init my-app-base --lib ```
-
编辑
Cargo.toml
修改crate-type
[lib]
name = "my_app_base"
crate-type = ["staticlib", "cdylib"]
```
Rust 构建出来的二进制库,在 IOS 中是静态链接进最终的程序之中,需要对构建 staticlib
的支持;在 Android 是通过动态链接在运行时装在进程序运行空间的,需要对构建 cdylib
的支持。
-
写一些符合 C ABI 的函数
src/lib.rs
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
-
添加代码
(直接在生成的项目上修改,暂不考虑代码设计问题,就简简单单的先把项目跑起来 )
import 'dart:ffi'; import 'package:ffi/ffi.dart'; // ... final dylib = Platform.isAndroid ? DynamicLibrary.open('libmy_app_base.so') :DynamicLibrary.process(); var hello = dylib.lookupFunction<Pointer<Utf8> Function(),Pointer<Utf8> 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 程序
参见 github.com/flutter/flu… 不要升级到 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 库,并提供了例子
-
用于构建 universal library
博文地址: idx0.dev/2020/02/15/…
转载自:https://juejin.cn/post/6844904065567031310