本文正在参加「金石计划 . 瓜分6万现金大奖」
[官文翻译]绑定Flutter和Rust高级的内存安全代码生成器 flutter_rust_bridge - 特性(三)-
官网:Introduction - flutter_rust_bridge (cjycode.com)
pub: flutter_rust_bridge | Dart Package (flutter-io.cn)
译时版本:1.49.1
原文链接:
- Sync in Dart - flutter_rust_bridge (cjycode.com)
- Concurrency - flutter_rust_bridge (cjycode.com)
- Handler - flutter_rust_bridge (cjycode.com)
- Initialization - flutter_rust_bridge (cjycode.com)
- Async in Rust - flutter_rust_bridge (cjycode.com)
- Multiple files - flutter_rust_bridge (cjycode.com)
- Run in build.rs - flutter_rust_bridge (cjycode.com)
- Cancellable tasks - flutter_rust_bridge (cjycode.com)
- Object pools - flutter_rust_bridge (cjycode.com)
- WASM - flutter_rust_bridge (cjycode.com)
- Miscellaneous - flutter_rust_bridge (cjycode.com)
- Logging - flutter_rust_bridge (cjycode.com)
Dart 中的同步
如果需要生成 Dart 中的同步函数,可以使用 SyncReturn<T>
作为返回类型。
建议只对很快的 Rust 函数使用,否则 Dart 的 UI 会被阻塞。
现在, SyncReturn
支持 String
、 Vec<u8>
和基本类型(bool
、 u8
、 u16
、 u32
、 u64
、 i8
、 i16
、 i32
、 i64
、 f32
、 f64
)。如果需要其它类型,请提出 issue 。
并发
多个 Rust 函数可以同时运行,它们会并发执行。 这是因为默认情况下使用线程池来执行 Rust 函数。 尽管如此,也可以完全定制该行为(甚至可以不用线程池)。
示例
考虑下面的 Rust 代码:
pub fn compute() {
thread::sleep(Duration::from_millis(1000));
}
然后下面的 Dart 代码会使用它:
var a = compute();
var b = compute();
var c = compute();
await Future.wait([a, b, c]); // 可能需要学习 Dart 中的 `Future` 和 `async` 来理解该点
这会花费 1 秒钟,而不是 3 秒钟 来完成运行,因为多个 compute
可以并发运行。
Handler
默认会使用 DefaultHandler
。也可以实现自己的 Handler
来做任何想做的事。
要这样做的话,需要在 Rust 的输入文件(可能用 lazy_static
)中创建一个名为 FLUTTER_RUST_BRIDGE_HANDLER
的变量。可能不需要创建全新的结构体来实现 Handler
,取而代之的是,使用 SimpleHandler
,然后定制它的泛型,如它的 Executor
。
示例
示例:除了向 Dart 报告错误之外也向后台报告错误
pub struct MyErrorHandler(ReportDartErrorHandler);
impl ErrorHandler for MyErrorHandler {
fn handle_error(&self, port: i64, error: handler::Error) {
send_error_to_your_backend(&error);
self.0.handle_error(port, error)
}
...
}
示例:执行开始和结束时打日志
pub struct MyExecutor(ThreadPoolExecutor<MyErrorHandler>);
impl Executor for MyExecutor {
fn execute<TaskFn, TaskRet>(&self, wrap_info: WrapInfo, task: TaskFn) {
let debug_name_string = wrap_info.debug_name.to_string();
self.thread_pool_executor
.execute(wrap_info, move |task_callback| {
Self::log_around(&debug_name_string, move || task(task_callback))
})
}
}
impl MyExecutor {
fn log_around<F, R>(debug_name: &str, f: F) -> R where F: FnOnce() -> R {
let start = Instant::now();
debug!("(Rust) execute [{}] start", debug_name);
let ret = f();
debug!("(Rust) execute [{}] end delta_time={}ms", debug_name, start.elapsed().as_millis());
ret
}
}
初始化
如果需要该特性,看一下 Dart 中的 FlutterRustBridgeSetupMixin
。
(会添加更多信息;如果现在有问题可以创建 issue 。)
Rust 中的异步
要从 Rust 函数中使用 async/await 或者返回一个 Future 类型,可以参考该文档。 如果对其它集成生成器也有兴趣,请提出 issue 。
多文件
当有一个大工程时,如果把所有内容都放在单个 api.rs
文件中通常会很低效,
取而代之的是可能想要分成 api_of_one_module.rs
、 api_of_another_module.rs
、等。
这也是有该特性的原因。
基本上,仅需要指定所有输入的 Rust 文件和所有的输出位置就可以了。 这里有个示例:
flutter_rust_bridge_codegen \
--rust-input "$REPO_DIR/native/src/api_1.rs" "$REPO_DIR/native/src/api_2.rs" \
--dart-output "$REPO_DIR/lib/bridge_generated_api_1.dart" "$REPO_DIR/lib/bridge_generated_api_2.dart" \
--class-name ApiClass1 ApiClass2 \
--rust-output generated_api_1 generated_api_2
更多细节看下该篇文章.
在 build.rs
中运行
有两种途径可以执行该代码生成器。
第一种且最明显的方式是直接在命令行运行 flutter_rust_bridge
。
第二种方式是集成到工程的 build.rs
中。
用这种方式,代码生成器会在编译 Rust 工程时自动触发。
示例配置可以看下这个 build.rs 文件。
可取消的任务
Rust代码的计算很重时,可能想要在过程中取消该任务。 例如,用户不再需要该处理。 这样可以节省宝贵的计算力。
安装:现在,该特性已完成,并且已经在自己(作者)的应用中使用了一些时间。(还没有将该PR合并到主分支中,仅仅是因为需要确定一下如果在 api.rs
中如何放置这些代码)。所以,访问 #333 然后把代码直接复制到工程中,像通常一样使用它即可。
对象池
在 Rust 端有一些大的对象时,可能不想在 Rust 和 Dart 之间一遍又一遍地复制。 这个时候对象池就有用了:只需要在 Rust 和 Dart 之间传递一个 "对象句柄"(实际上只是一些整数),然后 Rust 端会在该句柄和真正的对象之间进行转换。
安装:和 可取消的任务一样,看一下相关文档。
WASM
flutter_rust_bridge_codegen
也可以使用 wasm_bindgen
生成可在浏览器中运行的代码。
要生成 WASM 特定文件,需要在调用时传递两个选项:
flutter_rust_bridge_codegen .. --wasm --dart-decl-output <DECL>
DECL
是公用的类/函数声明文件。
例如,如果生成的Dart桥接内容输出到 lib/bridge_generated.dart
中,可以设置声明文件为 lib/bridge_definitions.dart
。
该操作默认会创建一些新文件:
├── lib
│ ├── bridge_definitions.dart
│ ├── bridge_generated.io.dart
│ └── bridge_generated.web.dart
└── native/src
├── bridge_generated.io.rs
└── bridge_generated.web.rs
.io
和 .web
模块实现了不同平台的辅助类。
由于 Dart 的模块系统,这种分割是强制的。
尽管如此,如果更喜欢在单个文件中保持 Rust Bridge ,也可以传递 --inline-rust
标志。
查看 Web集成 中如何消费 Web Bridge 的说明。
其它项目
从实现中分离生成的定义
生成的 bridge_generated.dart
默认会包含 API 的定义以及实现。
使用 --dart-decl-output
标志,可以分离开这两部分,定义不会包含任何内容如 dart:ffi
。
更多信息:#298.
日志
由于看到一些问题是问如何在 Flutter + Rust 应用中实现日志功能,这里有一些示例。
生产环境的日志
我自己的应用在生产环境下,使用了下面的策略用于 Rust 日志:使用通常的 Rust 日志方法,如 info!
和 debug!
宏。日志会在两个地方被消费:通过平台独有的方法打印出来(如 Android 的 Logcat 和 iOS 的 NSLog),也可用 Stream (流)将它们发送到 Dart 端,这样,Dart 代码和进一步的处理会使用相同的渠道将其作为 Dart 的日志(例,保存为文件、发送到服务器、等)
我的应用中 和日志相关的 完整 代码可以在这里看到:#486。
简单的日志记录器
Let us implement a simple logging system (adapted from the logging system I use with flutter_rust_bridge
in my app in production), where Rust code can send logs to Dart code.
让我们实现一个简单的日志系统(我的生产环境中的应用中使用了 flutter_rust_bridge
适配日志系统),
它可以从 Rust 代码向 Dart 代码发送日志。
Rust api.rs
:
pub struct LogEntry {
pub time_millis: i64,
pub level: i32,
pub tag: String,
pub msg: String,
}
// 为了演示已简化
// 编译的话,需要 OnceCell 或 Mutex 或 RwLock
// 可以看下 https://github.com/fzyzcjy/flutter_rust_bridge/issues/398
lazy_static! { static ref log_stream_sink: StreamSink<LogEntry>; }
pub fn create_log_stream(s: StreamSink<LogEntry>) {
stream_sink = s;
}
现在 Rust 很可能会抱怨因为没有为 LogEntry
实现 IntoDart
。
这正是所期望的,因为 flutter_rust_bridge
会生成这个行为的实现。
要改正这个错误,只需要返回 flutter_rust_bridge_codegen
。
生成的 Dart 代码:
Stream<LogEntry> createLogStream();
现在在 Dart 中使用它:
Future<void> setup() async {
createLogStream().listen((event) {
print('log from rust: ${event.level} ${event.tag} ${event.msg} ${event.timeMillis}');
});
}
现在可以愉快地在 Rust 中记录任何信息了:
log_stream_sink.add(LogEntry { msg: "hello I am a log from Rust", ... })
当然,也可以按照 包装数据流的 Sink 的 Rust 的 log
crate 实现一个日志记录器,然后使用如 info!
之类的标准的 Rust 日志机制。
我在我的工程中正是这样做的。
示例:简单的计数器
use anyhow::Result;
use std::{thread::sleep, time::Duration};
use flutter_rust_bridge::StreamSink;
const ONE_SECOND: Duration = Duration::from_secs(1);
// 这里还不能返回类型,这是个 BUG
pub fn tick(sink: StreamSink<i32>) -> Result<()> {
let mut ticks = 0;
loop {
sink.add(ticks);
sleep(ONE_SECOND);
if ticks == i32::MAX {
break;
}
ticks += 1;
}
Ok(())
}
在 Dart 中使用它:
import 'package:flutter/material.dart';
import 'ffi.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late Stream<int> ticks;
@override
void initState() {
super.initState();
ticks = api.tick();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text("Time since starting Rust stream"),
StreamBuilder<int>(
stream: ticks,
builder: (context, snap) {
final style = Theme.of(context).textTheme.headline4;
final error = snap.error;
if (error != null)
return Tooltip(
message: error.toString(),
child: Text('Error', style: style));
final data = snap.data;
if (data != null) return Text('$data second(s)', style: style);
return const CircularProgressIndicator();
},
)
],
),
),
);
}
}
本文正在参加「金石计划 . 瓜分6万现金大奖」