Flutter调用Rust代码操作指南

news/2024/11/24 10:58:41/

在之前的利用Rust与Flutter开发一款小工具文章中,我们使用Rust代码实现了一个简单的WebSocket发送功能。也在Rust库交叉编译以及在Android与iOS使用这篇中介绍了Rust库的打包以及双端的使用。

今天我们继续用之前WebSocket的代码举例,来介绍如何在Flutter项目中使用。

1.准备工作

本篇的主角就是flutter_rust_bridge,它是用于 FlutterRust 的高级内存安全绑定生成器。这个库只是一个代码生成器,帮助你的 Flutter / Dart 调用 Rust 函数。它只是生成了一些模板代码,代替了手工编写。

首先我们可以把Rust代码放到Flutter项目的根目录中,或者运行 cargo new --lib创建一个新的 Rust crate。完成后项目结构如下:

├── android
├── ios
├── lib
├── linux
├── macos
├── $crate
│   ├── Cargo.toml
│   └── src
├── test
├── web
└── windows

注意:将 crate 的根目录设为和其他项目同等级别,这样有助于简化配置过程。

稍微修改一下之前的rust代码(注意代码不要直接写在lib.rs中,不然生成文件无法获取导包):

这里我们将之前的代码放入api.rs中:

use std::collections::HashMap;
use std::sync::Mutex;
use ws::{connect, Handler, Sender, Handshake, Result, Message, CloseCode, Error};
use ws::util::Token;lazy_static! {static ref DATA_MAP: Mutex<HashMap<String, Sender>> = {let map: HashMap<String, Sender> = HashMap::new();Mutex::new(map)};
}struct Client {sender: Sender,host: String,
}impl Handler for Client {fn on_open(&mut self, _: Handshake) -> Result<()> {DATA_MAP.lock().unwrap().insert(self.host.to_owned(), self.sender.to_owned());Ok(())}fn on_message(&mut self, msg: Message) -> Result<()> {println!("<receive> '{}'. ", msg);Ok(())}fn on_close(&mut self, _code: CloseCode, _reasonn: &str) {DATA_MAP.lock().unwrap().remove(&self.host);}fn on_timeout(&mut self, _event: Token) -> Result<()> {DATA_MAP.lock().unwrap().remove(&self.host);self.sender.shutdown().expect("shutdown error");Ok(())}fn on_error(&mut self, _err: Error) {DATA_MAP.lock().unwrap().remove(&self.host);}fn on_shutdown(&mut self) {DATA_MAP.lock().unwrap().remove(&self.host);}}pub fn websocket_connect(host: String) {if let Err(err) = connect(host.to_owned(), |out| {Client {sender: out,host: host.to_owned(),}}) {println!("Failed to create WebSocket due to: {:?}", err);}
}pub fn send_message(host: String, message: String) {let binding = DATA_MAP.lock().unwrap();let sender = binding.get(&host.to_owned());match sender {Some(s) => {if s.send(message).is_err() {println!("Websocket couldn't queue an initial message.")};} ,None => println!("None")}
}pub fn websocket_disconnect(host: String) {DATA_MAP.lock().unwrap().remove(&host.to_owned());
}

api.rs

mod api;
#[macro_use]
extern crate lazy_static;

Cargo.toml配置如下:

[package]
name = "rust_demo"
version = "0.1.0"
edition = "2021"
publish = false[lib]
name = "rust_demo"
crate-type = ["staticlib", "cdylib"][profile.release]
lto = true
opt-level = 'z'
strip = true
codegen-units = 1
# panic = 'abort'[dependencies]
ws = "0.9.2"
lazy_static = "1.4.0"
flutter_rust_bridge = "=1.77.1"
flutter_rust_bridge_macros = "=1.77.1"[build-dependencies]
flutter_rust_bridge_codegen = "=1.77.1"

Flutter中pubspec.yaml的配置如下:

dependencies:flutter_rust_bridge: 1.77.1ffi: ^2.0.1dev_dependencies:ffigen: ^8.0.2

这里注意flutter_rust_bridge的版本需要一致。我这里目前使用的是1.77.1。然后在rust项目中执行:

cargo install flutter_rust_bridge_codegen
# 如果为iOS或MacOS应用构建
cargo install cargo-xcode
  • flutter_rust_bridge_codegen, 生成 Rust-Dart 胶水代码的核心。
  • ffigen, 从 C 头文件中生成 Dart 代码/
  • 安装 LLVM, 请看 Installing LLVM,ffigen 会使用到。
  • (可选) cargo-xcode,如果你想生成为 IOS 和 MacOS 的 Xcode 项目。

完成上面的准备工作,我们就可以在Flutter项目下执行命令,成功胶水代码了。

flutter_rust_bridge_codegen -r native/src/api.rs -d lib/ffi/rust_ffi.dart -c ios/Runner/bridge_generated.h
  • native/src/api.rs rust代码路径。
  • lib/ffi/rust_ffi.dart生成dart代码路径。
  • ios/Runner/bridge_generated.h创建的一个 C 头文件,里面列出了 Rust 库导出的所有符号,我们需要使用它确保 Xcode 不会将符号去除。

2.Android配置

首先安装cargo-ndk,它能够将代码编译到适合的 JNI 而不需要额外的配置。我们之前的文章中,就通过手动的方式在.cargo/config中配置clang链接器的路径。就比较繁琐,这个插件就是简化这一操作的。

安装命令:

// ndk低于22
cargo install cargo-ndk --version 2.6.0
// ndk高于22
cargo install cargo-ndk

交叉编译到安卓需要一些额外的组件,这个我们之前的文章也有说明:

rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android

接着,在 android/app/build.gradle 的最后添加下面几行:

[Debug: null,Profile: '--release',Release: '--release'
].each {def taskPostfix = it.keydef profileMode = it.valuetasks.whenTaskAdded { task ->if (task.name == "javaPreCompile$taskPostfix") {task.dependsOn "cargoBuild$taskPostfix"}}tasks.register("cargoBuild$taskPostfix", Exec) {workingDir "../../native"environment ANDROID_NDK_HOME: "$ANDROID_NDK"commandLine 'cargo', 'ndk',// the 2 ABIs below are used by real Android devices'-t', 'armeabi-v7a','-t', 'arm64-v8a','-o', '../android/app/src/main/jniLibs', 'build'if (profileMode != null) {args profileMode}}
}
  • ../../native就是rust代码路径。
  • ANDROID_NDK就是在android/gradle.properties配置的NDK路径。
  • Android每次运行都会打包rust代码并将so文件放入android/app/src/main/jniLibs下。所以如果Rust代码没有变化修改,可以在生成release文件后注释掉此处代码。
ANDROID_NDK=/Users/weilu/android/android-sdk-macosx/ndk/21.4.7075529

iOS配置

安装交叉编译组件:

rustup target add aarch64-apple-ios x86_64-apple-ios

接着在rust项目目录下执行cargo xcode。执行后,会生成一个xcodeproj后缀文件夹。它可以用于导入到其他 Xcode 项目中。
请添加图片描述
在 Xcode 中打开 ios/Runner.xcodeproj, 点击菜单File ---> Add Files to "Runner"接着把 xxx.xcodeproj 添加为子项目。

  • 点击 Runner 根项目,在Build PhasesTab下的Target Dependencies点击加号添加 $crate-staticlib文件。
    请添加图片描述
  • 接着,展开下面的 Link Binary With Libraries点击加号, 为 IOS 添加 lib$crate_static.a文件。
    请添加图片描述
    完成后如下图:
    请添加图片描述

绑定一开始生成的头文件bridge_generated.h

ios/Runner/Runner-Bridging-Header.h 中添加:bridge_generated.h

#import "GeneratedPluginRegistrant.h"
#import "bridge_generated.h"

ios/Runner/AppDelegate.swift 中添加dummy_method_to_enforce_bundling()

import UIKit
import Flutter@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {let dummy = dummy_method_to_enforce_bundling()print(dummy)GeneratedPluginRegistrant.register(with: self)return super.application(application, didFinishLaunchingWithOptions: launchOptions)}
}

Flutter调用

至此,配置工作已全部结束。下面我们看下如何调用,首先我们简单封装一个方法调用类。

import 'dart:ffi';
import 'dart:io';import 'package:flutter_ffi/ffi/rust_ffi.dart';class NativeFFI {NativeFFI._();static DynamicLibrary? _dyLib;static DynamicLibrary get dyLib {if (_dyLib != null) return _dyLib!;if (Platform.isIOS) {_dyLib = DynamicLibrary.process();} else if (Platform.isAndroid) {_dyLib = DynamicLibrary.open('librust_demo.so');} else {throw Exception('DynamicLibrary初始化失败');}return _dyLib!;}
}class NativeFun {static final _ffi = RustDemoImpl(NativeFFI.dyLib);static Future<void> websocketConnect(String host) async {return await _ffi.websocketConnect(host: host);}static Future<void> sendMessage(String host, String message) async {return await _ffi.sendMessage(host: host, message: message);}static Future<void> websocketDisconnect(String host) async {return await _ffi.websocketDisconnect(host: host);}
}

使用时,直接调用NativeFun.xxx()方法将可以了。


以上示例代码我已经提交到Github,有需要的可以运行查看。对你有帮助的话,点赞收藏起来~我们下个月再见!

参考

  • Flutter和Rust如何优雅的交互

  • flutter_rust_bridge中文版文档


http://www.ppmy.cn/news/323912.html

相关文章

荣耀游戏本散热实测分享 这是你的理想散热本

双11预期而至&#xff0c;今天是荣耀游戏本新一轮优惠预售的最后一天&#xff0c;考虑到很多小伙伴如今仍然不知道要入手什么游戏本&#xff0c;为此笔者特意进行了一次荣耀游戏本散热实测分享给大家&#xff0c;让大家可以更好地了解这款游戏本的散热优势&#xff0c;抓住优惠…

更薄的游戏本会吸引你吗? — 荣耀猎人评测

荣耀猎人昨天开卖的成绩不错&#xff0c;我们也终于完成了详细评测&#xff0c;下一波的抢购会在 10.2 日开启&#xff0c;其实在这个买什么都缺货的年代&#xff0c;只要有货就是赢了&#xff0c;它对得起现在的价格&#xff0c;希望大家都能买到自己想要的产品。 设计与体验 …

小米Redmi G游戏本怎么样

Redmi G游戏本正式发布了&#xff0c;作为redmi 的首款游戏本&#xff0c;redmi G采用了全新的设计&#xff0c;不同于之前redmi发布的轻薄本。这次游戏本更加具有个性&#xff0c;极具科幻的正脸&#xff0c;搭配游戏专属键盘&#xff0c;配合微米级3D精雕纹理工艺&#xff0c…

比 AMD 更便宜的 intel — Redmi G 游戏本详细测评

这就是 Redmi 旗下的首款笔记本,名字很好记:Redmi G. 我猜 G 也就是 Game 的意思吧,发布会结束公布价格之后,我的第一反应就是,终于有厂商把 intel 产品的售价拉下来了。 还是老样子,Redmi G 的设计和体验如何,性能以及散热的表现又怎么样?这里就是 MDT 对它进行的完整…

linux开发员用游戏本吗,游戏本算什么?老子能开发游戏

游戏本算什么&#xff1f;老子能开发游戏 2020-07-16 10:33:11 8点赞 6收藏 8评论 前言 相信大部分人对设计师本的理解还认为是一台拥有高素质屏幕和主流级处理器搭载可以支持视频硬件加速编码的主流级显卡的厚重笔记本产品。但是对于真正需要高性能的设计师们这种设计师本就有…

【B站最良心】2020年中9款热门游戏本最强横评!花8万块买下所有热门游戏本,华硕 天选 联想 拯救者 Y7000P R7000惠普 暗影精灵 6 Plus_哔哩哔哩 (゜-゜)つロ 干杯~-bili

【B站最良心】2020年中9款热门游戏本最强横评&#xff01;花8万块买下所有热门游戏本&#xff0c;华硕 天选 联想 拯救者 Y7000P R7000惠普 暗影精灵 6 Plus_哔哩哔哩 (゜-゜)つロ 干杯~-bilibilihttps://www.bilibili.com/video/BV1it4y1X7sj 这个噪音分贝说的不对&#xff0c…

雷神911T-Book Pro 游戏本参数配置 雷神 911T-Book Pro 评测

12 代酷睿 i5-12450H 处理器、16G 内存、512G 固态硬盘、RTX 2050 独显、100% sRGB 版本 4499 元 13 代酷睿 i5-13500H 处理器、16G 内存、512G 固态硬盘、RTX 3050 独显、120Hz 2.8K 屏版本 5999 元 该机采用了 12 核 16 线程、最高睿频 4.7GHz 的 13代英特尔酷睿 i5-13500H 标…

小米游戏本bios_年轻人的第一台游戏本?——小米游戏本2019评测

自从2018年3月27号小米发布第一台游戏本已经更新了两代了&#xff0c;从第一次八代前夕发七代&#xff0c;到后来八代版本悄然升级却无人问津&#xff0c;再到现在九代的赶上主流市场强势回归主流游戏本行列&#xff0c;模具没升级的情况下&#xff0c;价格也在之前的基础上有所…