赞
踩
在之前的利用Rust与Flutter开发一款小工具文章中,我们使用Rust代码实现了一个简单的WebSocket发送功能。也在Rust库交叉编译以及在Android与iOS使用这篇中介绍了Rust库的打包以及双端的使用。
今天我们继续用之前WebSocket的代码举例,来介绍如何在Flutter项目中使用。
本篇的主角就是flutter_rust_bridge,它是用于 Flutter
和 Rust
的高级内存安全绑定生成器。这个库只是一个代码生成器,帮助你的 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.1
ffi: ^2.0.1
dev_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 代码/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 不会将符号去除。首先安装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.key def profileMode = it.value tasks.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/app/src/main/jniLibs
下。所以如果Rust代码没有变化修改,可以在生成release文件后注释掉此处代码。ANDROID_NDK=/Users/weilu/android/android-sdk-macosx/ndk/21.4.7075529
安装交叉编译组件:
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 Phases
Tab下的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)
}
}
至此,配置工作已全部结束。下面我们看下如何调用,首先我们简单封装一个方法调用类。
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,有需要的可以运行查看。对你有帮助的话,点赞收藏起来~我们下个月再见!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。