当前位置:   article > 正文

【Rust blog】Rust + Flutter 高性能的跨端尝试

rust博客

本文为 heymind 的翻译投稿。

---

稍作配置,同一份代码横跨 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
  1. # Download targets for IOS ( 64 bit targets (real device & simulator) )
  2. rustup target add aarch64-apple-ios x86_64-apple-ios
  3. # Install cargo-lipo to generate the iOS universal library
  4. cargo install cargo-lipo
Android

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

  1. 获取 Android NDK

    sdkmanager --verbose ndk-bundle
    

    如果已经准备好了 Android NDK ,则设置环境变量 $ANDROID_NDK_HOME

    1. # example:
    2. export ANDROID_NDK_HOME=/Users/yinsiwei/Downloads/android-ndk-r20b
  2. Create the standalone NDK

    1. # $(pwd) == ~/Downloads
    2. git clone https://github.com/kennytm/rust-ios-android.git
    3. cd rust-ios-android
    4. ./create-ndk-standalone.sh
  3. 在 Cargo default config VS 配置 Android 交叉编译工具

    cat cargo-config.toml >> ~/.cargo/config
    

    执行上述命令后会在 Cargo 默认配置中,增加有关 Android 跨平台目标 (targets, aarch64-linux-androidarmv7-linux-androideabii686-linux-android) 的工具信息,指向刚刚创建的 standalone NDK

    1. [target.aarch64-linux-android]
    2. ar = ...
    3. linker = ..
    4. [target.armv7-linux-androideabi]
    5. ...
    6. [target.i686-linux-android]
    7. ..
  4. 下载 Rust 支持 Android 交叉编译的依赖

rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android
Start a simple rust library
  1. 创建一个 Rust 项目

 cargo init my-app-base --lib
  1. 编辑 Cargo.toml 修改 crate-type

  1. [lib]
  2. name = "my_app_base"
  3. crate-type = ["staticlib", "cdylib"]
Rust 构建出来的二进制库,在 IOS 中是静态链接进最终的程序之中,需要对构建 `staticlib` 的支持;在 Android 是通过动态链接在运行时装在进程序运行空间的,需要对构建 `cdylib` 的支持。
  1. 写一些符合 C ABI 的函数 src/lib.rs

    1. use std::os::raw::c_char;
    2. use std::ffi::CString;
    3. #[no_mangle]
    4. pub unsafe extern fn hello() -> *const c_char {
    5. let s = CString::new("world").unwrap();
    6. s.into_raw()
    7. }

    在上述代码中,每次当外部调用 hello 函数时,会在晋城堆空间中创建一个字符串 ( CString ),并将所有权 ( 释放该字符串所占堆空间的权利 ) 移交给调用者

Build libraries
  1. # IOS
  2. cargo lipo --release
  3. # Android
  4. cargo build --target aarch64-linux-android --release
  5. cargo build --target armv7-linux-androideabi --release
  6. cargo build --target i686-linux-android --release

然后在 target 目录下会得到以下有用的物料。

  1. target
  2. ├── aarch64-linux-android
  3. │   └── release
  4. │   ├── libmy_app_base.a
  5. │   └── libmy_app_base.so
  6. ├── armv7-linux-androideabi
  7. │   └── release
  8. │   ├── libmy_app_base.a
  9. │   └── libmy_app_base.so
  10. ├── i686-linux-android
  11. │   └── release
  12. │   ├── libmy_app_base.a
  13. │   └── libmy_app_base.so
  14. ├── universal
  15. │   └── release
  16. │   └── libmy_app_base.a

至此, Rust 部分就告于段落了。

Flutter Part

Copy build artifacts to flutter project

  1. from: target/universal/release/libmy_app_base.a
  2. to: ios/
  3. from: target/aarch64-linux-android/release/libmy_app_base.so
  4. to: android/app/src/main/jniLibs/arm64-v8a/
  5. from: target/armv7-linux-androideabi/release/libmy_app_base.so
  6. to: android/app/src/main/jniLibs/armeabi-v7a/
  7. from: target/i686-linux-android/release/libmy_app_base.so
  8. to: android/app/src/main/jniLibs/x86/

Call FFI function in Dart

  1. 添加依赖

    pubspec.yaml -> dev_dependencies: += ffi: ^0.1.3

  2. 添加代码

    (直接在生成的项目上修改,暂不考虑代码设计问题,就简简单单的先把项目跑起来 )

    1. import 'dart:ffi';
    2. import 'package:ffi/ffi.dart';
    3. // ...
    4. final dylib = Platform.isAndroid ? DynamicLibrary.open('libmy_app_base.so') :DynamicLibrary.process();
    5. var hello = dylib.lookupFunction<Pointer<Utf8> Function(),Pointer<Utf8> Function()>('hello');
    6. // ...
    7. hello();
    8. // -> world

Build Android Project

flutter run # 如果连接着 Android 设备就直接运行了起来

Build IOS Project

( 复杂了许多 )

  1. 跟随 Flutter 官方文档,配置 XCode 项目。

  2. 在 Build Phases 中 Link Binary With Libraries 添加 libmy_app_base.a 文件 (按照图上箭头点...) 

  3. 在 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
  1. dyld: Library not loaded: /b/s/w/ir/k/homebrew/Cellar/libimobiledevice-flutter/HEAD-398c120_3/lib/libimobiledevice.6.dylib
  2. Referenced from: /Users/hey/flutter/bin/cache/artifacts/libimobiledevice/idevice_id
  3. 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

  • https://github.com/kennytm/rust-ios-android

    介绍了如何构建出 Android, IOS 库,并提供了例子

  • https://github.com/TimNN/cargo-lipo

    用于构建 universal library

  • https://github.com/hanabi1224/flutter_native_extensions

原文链接:https://idx0.dev/2020/02/15/flutter-rust-1/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/121353
推荐阅读
相关标签
  

闽ICP备14008679号