稍作配置,同一份程式碼橫跨 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/…