8

Rust + Flutter 高性能的跨端尝试

 3 years ago
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

这里有一些行之有效的辅助脚本用于更加快捷配置交叉编译工具。

  1. 获取 Android NDK
    sh sdkmanager --verbose ndk-bundle 如果已经准备好了 Android NDK ,则设置环境变量 $ANDROID_NDK_HOME ```sh
    example:
    export ANDROID_NDK_HOME=/Users/yinsiwei/Downloads/android-ndk-r20b 2. Create the standalone NDKsh
    $(pwd) == ~/Downloads
    git clone https://github.com/kennytm/rust-ios-android.git cd rust-ios-android ./create-ndk-standalone.sh 3. 在 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

  1. 创建一个 Rust 项目
    sh cargo init my-app-base --lib
  2. 编辑 Cargo.toml 修改 crate-type
    ini [lib] name = "my_app_base" crate-type = ["staticlib", "cdylib"] Rust 构建出来的二进制库,在 IOS 中是静态链接进最终的程序之中,需要对构建 staticlib 的支持;在 Android 是通过动态链接在运行时装在进程序运行空间的,需要对构建 cdylib 的支持。
  3. 写一些符合 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

  1. 添加依赖
    pubspec.yaml -> dev_dependencies: += ffi: ^0.1.3
  2. 添加代码
    (直接在生成的项目上修改,暂不考虑代码设计问题,就简简单单的先把项目跑起来 ) ```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

( 复杂了许多 )

  1. 跟随 Flutter 官方文档,配置 XCode 项目。
  2. Build PhasesLink Binary With Libraries 添加 libmy_app_base.a 文件 (按照图上箭头点...)
  1. Build SettingsOther Linker Flags 中添加 force_load 的参数。

这是由于在 Dart 中通过动态的方式调用了该库的相关函数,但在编译期间静态分析的时候,这些都是未曾被调用过的无用函数,就被剪裁掉了。要通过 force_load 方式解决这个问题。

Result

v2-5dbdfd2f43bf2186abe37c802bee0815_b.jpg


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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK