在之前的利用Rust與Flutter開發(fā)一款小工具文章中,我們使用Rust代碼實(shí)現(xiàn)了一個(gè)簡單的WebSocket發(fā)送功能。也在Rust庫交叉編譯以及在Android與iOS使用這篇中介紹了Rust庫的打包以及雙端的使用。
今天我們繼續(xù)用之前WebSocket的代碼舉例,來介紹如何在Flutter項(xiàng)目中使用。
準(zhǔn)備工作
本篇的主角就是flutter_rust_bridge,它是用于 Flutter
和 Rust
的高級(jí)內(nèi)存安全綁定生成器。這個(gè)庫只是一個(gè)代碼生成器,幫助你的 Flutter / Dart
調(diào)用 Rust
函數(shù)。它只是生成了一些模板代碼,代替了手工編寫。
首先我們可以把Rust代碼放到Flutter項(xiàng)目的根目錄中,或者運(yùn)行 cargo new --lib
創(chuàng)建一個(gè)新的 Rust crate。完成后項(xiàng)目結(jié)構(gòu)如下:
├── android
├── ios
├── lib
├── linux
├── macos
├── $crate
│ ├── Cargo.toml
│ └── src
├── test
├── web
└── windows
注意:將 crate 的根目錄設(shè)為和其他項(xiàng)目同等級(jí)別,這樣有助于簡化配置過程。
稍微修改一下之前的rust代碼(注意代碼不要直接寫在lib.rs中,不然生成文件無法獲取導(dǎo)包):
這里我們將之前的代碼放入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項(xiàng)目中執(zhí)行:
cargo install flutter_rust_bridge_codegen
# 如果為iOS或MacOS應(yīng)用構(gòu)建
cargo install cargo-xcode
-
flutter_rust_bridge_codegen
, 生成 Rust-Dart 膠水代碼的核心。 -
ffigen
, 從 C 頭文件中生成 Dart 代碼/ - 安裝 LLVM, 請(qǐng)看 Installing LLVM,ffigen 會(huì)使用到。
- (可選)
cargo-xcode
,如果你想生成為 IOS 和 MacOS 的 Xcode 項(xiàng)目。
完成上面的準(zhǔn)備工作,我們就可以在Flutter項(xiàng)目下執(zhí)行命令,成功膠水代碼了。
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
創(chuàng)建的一個(gè) C 頭文件,里面列出了 Rust 庫導(dǎo)出的所有符號(hào),我們需要使用它確保 Xcode 不會(huì)將符號(hào)去除。
Android配置
首先安裝cargo-ndk
,它能夠?qū)⒋a編譯到適合的 JNI 而不需要額外的配置。我們之前的文章中,就通過手動(dòng)的方式在.cargo/config
中配置clang鏈接器的路徑。就比較繁瑣,這個(gè)插件就是簡化這一操作的。
安裝命令:
// ndk低于22
cargo install cargo-ndk --version 2.6.0
// ndk高于22
cargo install cargo-ndk
交叉編譯到安卓需要一些額外的組件,這個(gè)我們之前的文章也有說明:
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每次運(yùn)行都會(huì)打包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項(xiàng)目目錄下執(zhí)行cargo xcode
。執(zhí)行后,會(huì)生成一個(gè)xcodeproj
后綴文件夾。它可以用于導(dǎo)入到其他 Xcode 項(xiàng)目中。
在 Xcode 中打開 ios/Runner.xcodeproj
, 點(diǎn)擊菜單File ---> Add Files to "Runner"
接著把 xxx.xcodeproj
添加為子項(xiàng)目。
- 點(diǎn)擊
Runner
根項(xiàng)目,在Build Phases
Tab下的Target Dependencies
點(diǎn)擊加號(hào)添加$crate-staticlib
文件。 - 接著,展開下面的
Link Binary With Libraries
點(diǎn)擊加號(hào), 為 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調(diào)用
至此,配置工作已全部結(jié)束。下面我們看下如何調(diào)用,首先我們簡單封裝一個(gè)方法調(diào)用類。
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);
}
}
使用時(shí),直接調(diào)用NativeFun.xxx()
方法將可以了。
以上示例代碼我已經(jīng)提交到Github,有需要的可以運(yùn)行查看。對(duì)你有幫助的話,點(diǎn)贊收藏起來~我們下個(gè)月再見!
參考
-
Flutter和Rust如何優(yōu)雅的交互文章來源:http://www.zghlxwxcb.cn/news/detail-486517.html
-
flutter_rust_bridge中文版文檔文章來源地址http://www.zghlxwxcb.cn/news/detail-486517.html
到了這里,關(guān)于Flutter調(diào)用Rust代碼操作指南的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!