Flutter&Rust#01 | 突破能力瓶颈

4,758 阅读8分钟

Flutter&rust1.png

Flutter&Rust#01系列文章:


Rust 是和 C++ 同一级别的系统级编程语言,而 Flutter 是 全平台应用 开发框架。当两者相遇,会碰撞出什么火花呢? Flutter&Rust 开发全平台应用程序的技术,已经存在非常非常久。最著名的当属 rustdesk 开源项目:

image.png


一、Flutter 为什么需要 Rust

任何高级语言,在面临图片处理、音视频处理、复杂解析、AI 算法等复杂运算时,都会显得力不从心。往往都会接入 C/C++ 来实现功能。比如:

  • Android 的 NDK 开发实现 Java/Kotlin 调用 C/C++ ;
  • iOS 和 MacOS 也可以通过 Objective/Swift 与 C/C++ 混编;
  • Python 通过 Cython 实现 与 C/C++ 的交互;
  • Web 可以通过 WASM 与 C/C++ 的交互;
  • Windows 开发本身就是使用 C/C++ ;

Flutter 的主流开发语言 Dart 在某些高性能的场景下通用不能胜任。但它可以通过 ffi 调用其他语言来实现功能。其他语言当然可以是 C/C++ 。但是 Rust 正在逐渐流行起来,作为一个 C/C++ 之外的系统级高性能编程语言的可选项,受到了广泛的关注。

Flutter & Rust 不失为一个如虎添翼的选择。


1. 环境准备

既然是 Flutter&Rust 应用开发,安装环境是必不可少的。

通过命令可以查看环境是否正确、目前版本如下:

·]>>  flutter --version
Flutter 3.24.0  channel stable  https://github.com/flutter/flutter.git
Framework  revision 80c2e84975 (5 weeks ago)  2024-07-30 23:06:49 +0700
Engine  revision b8800d88be
Tools  Dart 3.5.0  DevTools 2.37.2

·]>>  rustup --version
rustup 1.27.1 (54dd3d00f 2024-04-24)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.81.0 (eeb90cda1 2024-09-04)`

2. 安装flutter&rust桥接工具

flutter_rust_bridge 插件是 Flutter 和 rust 沟通的桥梁,它可以自动生成相关的 Dart 代码。通过 cargo 全局安装命令行工具

cargo install flutter_rust_bridge_codegen

接下来可以通过 flutter_rust_bridge_codegen 创建 Flutter 项目,它会自动生成支持 rust 开发环境的 Flutter 模版项目。比如这里创建一个名为 blade 的项目:

flutter_rust_bridge_codegen create blade

项目的代码结构如下所示,和普通的 Flutter 项目区别在于多了 rust 包用于盛放 rust 代码:

image.png

Flutter&Rust 直接通过 Dart ffi 调用 rust 生成的 动态链接库 执行功能,所以可以大大减少平台习惯性。项目中的 rust_builder 本地依赖包,其价值在于直接编译出不同平台的 动态链接库,一站式服务,无需开发者手动处理。


3. 构建应用产物

我们可以直接通过 flutter 命令来构建应用程序产物,比如下面构建 Windows 和 Android 应用程序。可以直接运行或安装

flutter build apk --target-platform android-arm64

image.png

flutter build windows

image.png

同理,iOS、MacOS、Linux 打包时也会直接生成对应产物:

Android 项目Windows 项目
image.png

二、Flutter&Rust 项目解读

可以看出,基于 flutter_rust_bridge 我们可以非常方便地集成 Rust 代码进行开发。这样就可以像 Android 的 NDK 一样,通过接入系统级的语言,突破框架本身的语言性能瓶颈。


1.从产物看 Flutter&Rust 的效力

从构建的产物来看,windows 应用中有一个 rust_lib_blade.dll ,这个就是 rust 代码编译成的 Windows 动态链接库:

image.png

同样,Android 的 apk 中也有 rust_lib_blade.dll,它是 rust 代码编译成的 Android 平台的动态链接库。

image.png

也就是说 rust_builder 会将 rust 代码自动构建成对应平台的 动态链接库。然后 Dart 代码通过 ffi 调用动态链接库的功能。从而实现 Flutter&Rust 联合开发。


2. 动态链接库的使用

在 Flutter 代码层,多了src/rust 代码,它是 flutter_rust_bridge 自动生成的。在 main 方法中通过 await RustLib.init() 初始化各平台动态链接库:

image.png

这里 RustLib 也是自动生成的 dart 代码,它是一个单例对象,通过 init 静态方法,会帮我们处理各个平台动态链接库的加载逻辑:

image.png


3. Dart 调用 Rust 代码

在 api 文件夹中盛放着供 Dart 使用的 Rust 功能接口,比如这里的 greet 方法就是通过 rust 代码获取的字符串信息。也就是应用界面中的文字:

image.png

---->[src/rust/api/simple.dart]----
String greet({required String name}) =>
    RustLib.instance.api.crateApiSimpleGreet(name: name);

对应的 rust 相关函数可以在 rust 项目中看到:

---->[rust/src/api/simple.rs]----
#[flutter_rust_bridge::frb(sync)] // Synchronous mode for simplicity of the demo
pub fn greet(name: String) -> String {
    format!("Hello, {name}!")
}

#[flutter_rust_bridge::frb(init)]
pub fn init_app() {
    // Default utilities - feel free to customize
    flutter_rust_bridge::setup_default_user_utils();
}


三、增加 Rust 项目功能

目前 rust 代码已经成功在 Flutter 的土壤中生根发芽,产生效力,接下来看一下想要增加新的功能时,该做些什么。


1.增加新的 rust 功能

下面来看一下如何增加新的 rust 功能代码,比如先来个简单的数值运算器。我们可以在 RustRover 中打开 rust 项目,进行编码和测试。保证 rust 端代码的正确性:

image.png

如下所示,在 api 中增加一个 basic4.rs 模块测试四则运算

image.png

#[flutter_rust_bridge::frb(sync)]
pub fn add(x: i32, y: i32) -> i32 {
    x + y
}

2.重新生成桥接代码

在 Flutter 项目的根目录执行下面的命令,就可以重新生成桥接代码:
: 如果命令卡住了,可能需要控制台可以科学上网。

flutter_rust_bridge_codegen generate

image.png

这样 dart 端就可以自动生成桥接 rust 的 add 函数,Flutter 项目中就可以使用了。


3.小试牛刀: 获取设备 CPU 架构

下面通过一个使用的功能,展示一下 rust 的能力。我们知道,不同设备有其对应的 cpu 架构。如何在运行时获取到这个信息呢?比如有的 Android 是 aarch64,有的是 armeabi-v7a ; windows 一般是 x86-64 。 rust 的 sysinfo 库可以获取很多系统信息,也支持各大主流平台:

image.png

首先在 rust 项目中添加 sysinfo 依赖:

[dependencies]
flutter_rust_bridge = "=2.3.0"
sysinfo = "0.31.4"

然后提供 rust 层的代码,通过 cpu_arch 函数,完成功能:

use sysinfo::System;
use crate::system::model::SystemInfo;

#[flutter_rust_bridge::frb(sync)]
pub fn cpu_arch() -> String {
    System::cpu_arch().unwrap()
}

然后重新生成代码,就可以在 Flutter 界面项目中使用相关函数得到 CPU 架构。这样就通过 Rust 正 Flutter 中解决了一个真实的问题。

windows平台Android 平台
image.pngimage.png

四、在已有项目中集成 Rust

最后,来看一下如何在已有的 Flutter 项目中集成 Rust 。比如我的 匠心千刃 项目中,希望增加查看当前设备信息的功能界面,如下所示:

image.png


1. 如何构建动态链接库

首先,要明白 Rust 接入 Flutter 的方式是通过 ffi 访问动态链接库。所以如何生成动态链接库是最关键的一步。自动生成的 rust_builder 就是处理这个事的,通过其中的 cargokit 交叉编译出各个平台动态链接库:

image.png

所以把 rust_builderrust 项目拷贝到当前 Flutter 项目中; 在 yaml中配置依赖,即可集成编译环境:

image.png

dependencies:
  #...
  flutter_rust_bridge: 2.3.0
  rust_lib_blade:
    path: rust_builder

2. 代码生成器的配置

接下来就是如何让当前项目支持 flutter_rust_bridge_codegen 自动生成代码。很简单,只需要在 Flutter 的根目录下放一个 flutter_rust_bridge.yaml ,配置一下输入输出信息即可。注意 dart_output 文件夹一定要存在,否则创建会失败:

rust_input: crate::api
rust_root: rust/
dart_output: lib/src/rust

image.png

到这里,一个 Flutter 项目就支持了 Rust 代码。


3. 功能实现

rust 代码中定义 SystemInfo 类型承载我们期望的数据,并通过 Serialize 和 Deserialize 支持序列化和反序列化。我们的方法将返回 json 数据给 Flutter 使用:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct SystemInfo {
    pub(crate) name: String,
    pub(crate) kernel_version: String,
    pub(crate) os_version: String,
    pub(crate) long_os_version: String,
    pub(crate) host_name: String,
    pub(crate) cpu_arch: String,
    pub(crate) distribution_id: String,
    pub(crate) boot_time: u64,
    pub(crate) uptime: u64,
}

然后提供 get_sys_info 函数处理具体逻辑,返回 String 字符串:最后通过 api 定义接口函数供 Flutter使用即可:

image.png

pub fn get_sys_info() -> String {
    let name = System::name().unwrap();
    let kernel_version = System::kernel_version().unwrap();
    let os_version = System::os_version().unwrap();
    let long_os_version = System::long_os_version().unwrap();
    let host_name = System::host_name().unwrap();
    let cpu_arch = System::cpu_arch().unwrap();
    let distribution_id = System::distribution_id();
    let boot_time = System::boot_time();
    let uptime =  System::uptime();
    let info = SystemInfo {
        name,
        kernel_version,
        os_version,
        long_os_version,
        host_name,
        cpu_arch,
        distribution_id,
        boot_time,
        uptime,
    };
    serde_json::to_string(&info).unwrap()
}

Flutter 端通过 sysInfo 方法获取 json 字符串后,使用 jsonDecode 解码为 Map 对象展示即可:

image.png


到这里,我们就完成了 Flutter&Rust 项目的使用,让 Rust 的种子可以在 Flutter 的环境里生根发芽。后期匠心千刃中涉及到复杂计算、图片、音视频处理时。都可以通过 rust 来提高 Flutter 本身的能力瓶颈,这就是 Dart FFI 的价值所在。
后期对 匠心千刃 项目的推进,也会带来更多 Flutter&Rust 的技术探索,敬请期待。那本文就到这里,谢谢观看 ~