I 前置介绍
Tauri
Tauri 是一个框架,用于为所有主要桌面平台构建微小、极快的二进制文件。 开发人员可以集成任何可编译为HTML、JS 和CSS 的前端框架来构建他们的用户界面。 应用程序的后端是一个来自rust 的二进制文件,带有一个前端可以与之交互的API。
Flutter
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。 在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的,可以用一套代码同时构建Android\iOS\Windows\Linux\macOS应用,性能可以达到原生应用一样的性能。
Flutter_rust_bridge
用于 Flutter 和 Rust 的高级内存安全绑定生成器
II 问题记录
macOS支持问题
不支持macOS下打开wry子窗口,错误如下
- 代码,macOS下EventLoop 没有 new_any_thread 函数
fn show_webview(url: String) {
use wry::{
application::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
},
webview::WebViewBuilder,
};
let event_loop = EventLoop::<()>::with_user_event();
let window = WindowBuilder::new()
.with_title("Hello World")
// .with_window_icon(false)
.build(&event_loop)
.unwrap();
let _webview = WebViewBuilder::new(window)
.unwrap()
.with_url(&url)
.unwrap()
.build()
.unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}
错误
thread 'frb_workerpool' panicked at 'On macOS, `EventLoop` must be created on the main thread!' , /Users/apanda/.cargo/registry/src/mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b/tao-0.15.4/src/platform_impl/macos/event_loop.rs:119:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: FfiException(PANIC_ERROR, On macOS, `EventLoop` must be created on the main thread!, null) #0 FlutterRustBridgeBase._transformRust2DartMessage (package:flutter_rust_bridge/src/basic.dart:101:9) #1 FlutterRustBridgeBase.executeNormal.<anonymous closure> (package:flutter_rust_bridge/src/basic.dart:49:9) <asynchronous suspension>
关键字 On macOS, EventLoop must be created on the main thread!
- 亲测在Windows下正常调用
fn show_webview(url: String) {
use wry::{
application::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
},
webview::WebViewBuilder,
};
let event_loop = EventLoop::<()>::new_any_thread();
let window = WindowBuilder::new()
.with_title("Hello World")
// .with_window_icon(false)
.build(&event_loop)
.unwrap();
let _webview = WebViewBuilder::new(window)
.unwrap()
.with_url(&url)
.unwrap()
.build()
.unwrap();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}
Flutter_rust_bridge 启动问题
按照Flutter_rust_bridge文档集成完成后,
- 在终端运行
flutter run
- 报错
Building macOS application...
dyld[17757]: Library not loaded: '/usr/local/lib/rust.dylib'
Referenced from: '/Users/apanda/MEGAsync/Projects/flutter/FlutterProjects/toolbox/build/macos/Build/Products/Debug/ToolBox.app/Contents/MacOS/ToolBox'
Reason: tried: '/usr/local/lib/rust.dylib' (no such file), '/usr/lib/rust.dylib' (no such file)
Error waiting for a debug connection: The log reader stopped unexpectedly, or never started.
Error launching application on macOS.
- 应用也会弹出错误提示
-
解决思路
1、通过XCode启动项目,启动成功后,查看控制台输出
- 拿到 Dart VM service 地址:http://127.0.0.1:60419/ps3b1UPu-Fs=/
2022-11-18 15:19:45.196617+0800 ToolBox[23009:3084138] Metal API Validation Enabled flutter: The Dart VM service is listening on http://127.0.0.1:60419/ps3b1UPu-Fs=/ flutter: ╔════════════════════════════════════════════════════╗ flutter: ║ ISAR CONNECT STARTED ║ flutter: ╟────────────────────────────────────────────────────╢ flutter: ║ Open the link to connect to the Isar ║ flutter: ║ Inspector while this build is running. ║ flutter: ╟────────────────────────────────────────────────────╢ flutter: ║ https://inspect.isar.dev/3.0.5/ #/60419/ps3b1UPu-Fs ║ flutter: ╚════════════════════════════════════════════════════╝ flutter:
- 2、在项目根目录执行命令
futter attach --debug-url=http://127.0.0.1:60419/ps3b1UPu-Fs=/
# 输出如下
Syncing files to device macOS... 22.8s
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
💪 Running with sound null safety 💪
An Observatory debugger and profiler on macOS is available at: http://127.0.0.1:60654/6CzYD5OKqko=/
The Flutter DevTools debugger and profiler on macOS is available at: http://127.0.0.1:9102?uri=http://127.0.0.1:60654/6CzYD5OKqko=/
又到了熟悉的终端输出与操作了。
III 总结
To Flutter_rust_bridge
总体来说,flutter_rust_bridge 给flutter端提供了 另外一个思路,对于需要同时编写macOS Android iOS Windows 以及linux 多个端的插件而言,使用 Rust 无疑是更加简便,同时也对非各端专业人员提供了一个更快的捷径去实现多端的功能。
To Taury
因为之前有实践过一个项目Angular+Tauri的桌面应用,表现比Electron更加出色,客户的接受度更高。而这次Flutter接入Rust 本意是想实践一个Flutter Desktop的webview环境。
由于在macOS下使用的Safari的内核,Windows下使用的webview2 的内核。Tauri的兼容性会差一点,但是我在使用过程中以及生产项目中暂无出现兼容性问题。
想从Tauri中借鉴一些思路,直接发现了wry的库。
而Tauri的 wry 库,更确切的说是一个调用系统的webview功能展示html控件的方式。 不是全功能的浏览器(非Electron)接入,提供了更小内存、更小打包的方式。
IV 小技巧
在使用Flutter_rust_bridge过程中,桥接方法如果暴露太多影响美观与架构 ,可以借鉴接口方式,入参 request ,返回response的逻辑来统一处理桥接函数,其他的功能在rust内部进行私有 。
例如:
// 桥接函数
pub fn request(req: Request) -> Response {
if req.rust_type == 0 {
return fun1(req.data);
}
if req.rust_type == 1 {
return fun2(req.data);
}
return Response {
succeed: true,
data: String::from("hello world"),
};
}
// 具体处理业务的函数不暴露 这样生成的代码也不会包含
fn fun1()->Request{xxxx}
fn fun2()->Request{xxxx}
// 入参 Request
pub struct Request {
pub rust_type: u32,
pub data: String,
}
// 返回 Response
pub struct Response {
pub succeed: bool,
pub data: String,
}
// 自动生成的 bridge_generated.dart
abstract class Rust {
Future<Response> request({required Request req, dynamic hint});
FlutterRustBridgeTaskConstMeta get kRequestConstMeta;
}
class Request {
final int rustType;
final String data;
Request({
required this.rustType,
required this.data,
});
}
class Response {
final bool succeed;
final String data;
Response({
required this.succeed,
required this.data,
});
}
class RustImpl implements Rust {
final RustPlatform _platform;
factory RustImpl(ExternalLibrary dylib) => RustImpl.raw(RustPlatform(dylib));
/// Only valid on web/WASM platforms.
factory RustImpl.wasm(FutureOr<WasmModule> module) => RustImpl(module as ExternalLibrary);
RustImpl.raw(this._platform);
Future<Response> request({required Request req, dynamic hint}) => _platform.executeNormal(FlutterRustBridgeTask(
callFfi: (port_) => _platform.inner.wire_request(port_, _platform.api2wire_box_autoadd_request(req)),
parseSuccessData: _wire2api_response,
constMeta: kRequestConstMeta,
argValues: [req],
hint: hint,
));
FlutterRustBridgeTaskConstMeta get kRequestConstMeta => const FlutterRustBridgeTaskConstMeta(
debugName: "request",
argNames: ["req"],
);
xxxxx....
}
通过ffi处理后调用
// ffi 处理
const _base = 'rust';
final _dylib = io.Platform.isWindows ? '$_base.dll' : 'lib$_base.so';
final Rust api = RustImpl(io.Platform.isIOS || io.Platform.isMacOS ? DynamicLibrary.executable() : DynamicLibrary.open(_dylib));
// 实际调用
api.request(req: Request(rustType: 1, data: "https://blog.apanda.online"));