1 个不稳定版本

0.1.1 2022年11月15日

#17#background-thread

Download history 778/week @ 2024-03-14 1124/week @ 2024-03-21 1003/week @ 2024-03-28 1479/week @ 2024-04-04 1670/week @ 2024-04-11 1373/week @ 2024-04-18 1071/week @ 2024-04-25 1316/week @ 2024-05-02 1457/week @ 2024-05-09 1988/week @ 2024-05-16 1410/week @ 2024-05-23 1583/week @ 2024-05-30 1167/week @ 2024-06-06 1027/week @ 2024-06-13 1141/week @ 2024-06-20 612/week @ 2024-06-27

4,178 每月下载次数
用于 irondash_message_channel

MIT/Apache

51KB
1K SLoC

irondash_message_channel

Rust-dart桥接器,类似于Flutter的平台通道。

此包允许使用类似Flutter平台通道的模式从Dart调用Rust代码,反之亦然。

  • 易于使用的便捷API(Dart端模仿平台通道API)。
  • 高性能
    • 从Rust调用Dart时二进制数据无复制
    • 从Dart调用Rust时二进制数据精确复制一次
  • Rust宏用于自动序列化和反序列化(类似于Serde但针对零复制进行了优化)
  • 无需代码生成
  • 线程亲和性 - Rust通道对应项绑定到创建通道的线程。只要运行RunLoop,就可以在平台线程或任何后台线程上拥有通道。
  • 最终化处理器 - Rust端可以在Dart对象被垃圾回收时收到通知。
  • 异步支持

用法

初始设置

因为Rust代码需要访问Dart FFI API,所以需要进行一些设置。

/// initialize context for Native library.
MessageChannelContext _initNativeContext() {
    final dylib = defaultTargetPlatform == TargetPlatform.android
        ? DynamicLibrary.open("libmyexample.so")
        : (defaultTargetPlatform == TargetPlatform.windows
            ? DynamicLibrary.open("myexample.dll")
            : DynamicLibrary.process());

    // This function will be called by MessageChannel with opaque FFI
    // initialization data. From it you should call
    // `irondash_init_message_channel_context` and do any other initialization,
    // i.e. register rust method channel handlers.
    final function =
        dylib.lookup<NativeFunction<MessageChannelContextInitFunction>>(
            "my_example_init_message_channel_context");
    return MessageChannelContext.forInitFunction(function);
}

final nativeContext = _initNativeContext();

// Now you can create method channels

final _channel =
    NativeMethodChannel('my_method_channel', context: nativeContext);

_channel.setMethodCallHandler(...);

Rust端

use irondash_message_channel::*;

#[no_mangle]
pub extern "C" fn my_example_init_message_channel_context(data: *mut c_void) -> FunctionResult {
    irondash_init_message_channel_context(data)
}

简单用法

设置完成后,您可以使用Dart NativeMethodChannel,类似于Flutter的PlatformChannel


final _channel = NativeMethodChannel('my_method_channel', context: nativeContext);

_channel.setMessageHandler((call) async {
    if (call.method == 'myMethod') {
        return 'myResult';
    }
    return null;
});

final res = await _channel.invokeMethod('someMethod', 'someArg');

在Rust端,您可以实现对非异步版本的MethodHandler trait,或者如果您想使用异步/await,可以使用AsyncMethodHandler

use irondash_message_channel::*;

struct MyHandler {}

impl MethodHandler for MyHandler {
    fn on_method_call(&self, call: MethodCall, reply: MethodCallReply) {
        match call.method.as_str() {
            "getMeaningOfUniverse" => {
                reply.send_ok(42);
            }
            _ => reply.send_error(
                "invalid_method".into(),
                Some(format!("Unknown Method: {}", call.method)),
                Value::Null,
            ),
        }
    }
}

fn init() {
    let handler = MyHandler {}.register("my_method_channel");
    // make sure handler is not dropped, otherwise it can't handle method calls.
}

或异步版本

use irondash_message_channel::*;

struct MyHandler {}

#[async_trait(?Send)]
impl AsyncMethodHandler for MyHandler {
    async fn on_method_call(&self, call: MethodCall) -> PlatformResult {
        match call.method.as_str() {
            "getMeaningOfUniverse" => {
                Ok(42.into())
            }
            _ => Err(PlatformError {
                code: "invalid_method".into(),
                message: Some(format!("Unknown Method: {}", call.method)),
                detail: Value::Null,
            })),
        }
    }
}

fn init() {
    let handler = MyHandler {}.register("my_method_channel");
    // make sure handler is not dropped, otherwise it can't handle method calls.
}

从Rust调用Dart

use irondash_message_channel::*;

struct MyHandler {
    invoker: Late<AsyncMethodInvoker>,
}

#[async_trait(?Send)]
impl AsyncMethodHandler for MyHandler {
    // This will be called right after method channel registration.
    // You can use invoker to call Dart methods handlers.
    fn assign_invoker(&self, invoker: AsyncMethodInvoker) {
        self.invoker.set(invoker);
    }

    // ...
}

请注意,要使用Invoker,您需要知道目标isolateId。您可以从处理Rust中方法调用时的MethodCall结构中获取它。您还可以在isolate销毁时收到通知

impl MethodHandler for MyHandler {
    /// Called when isolate is about to be destroyed.
    fn on_isolate_destroyed(&self, _isolate: IsolateId) {}
    // ...

要查看消息通道的实际应用,请查看示例项目

线程考虑

MethodHandlerAsyncMethodHandler 与它们创建的线程绑定。这个线程必须运行一个 RunLoop。对于平台线程,这是隐含的。要在后台线程使用通道,您需要创建一个 RunLoop 并自行运行。

MethodInvokerSend。它可以在线程之间传递,方法调用响应将在请求发送的同一线程上接收。同样,线程必须有一个正在运行的 RunLoop

转换到和从 Value

Value 代表了可以在 Rust 和 Dart 之间传递的所有类型。为了简化 Rust 端的序列化和反序列化,irondash_message_channel 提供了 IntoValueTryFromValue 进程宏,为 Value 生成 TryInto<YourStruct>From<YourStruct> 特性。这是一个可选功能。

[dependencies]
irondash_message_channel = { version = "0.1.0", features = ["derive"] }
#[derive(TryFromValue, IntoValue)]
struct AdditionRequest {
    a: f64,
    b: f64,
}

#[derive(IntoValue)]
struct AdditionResponse {
    result: f64,
    request: AdditionRequest,
}

let value: Value = get_value_from_somewhere();
let request: AdditionRequest = value.try_into()?;
let response: Value = AdditionResponse {
    result: request.a + request.b,
    request,
}.into();

还支持更高级的映射选项,例如

#[derive(IntoValue, TryFromValue)]
#[irondash(tag = "t", content = "c")]
#[irondash(rename_all = "UPPERCASE")]
enum Enum3CustomTagContent {
    Abc,
    #[irondash(rename = "_Def")]
    Def,
    SingleValue(i64),
    #[irondash(rename = "_DoubleValue")]
    DoubleValue(f64, f64),
    Xyz {
        x: i64,
        s: String,
        z1: Option<i64>,
        #[irondash(skip_if_empty)]
        z2: Option<i64>,
        z3: Option<f64>,
    },
}

与 serde 不同,.into()try_into() 消耗原始值,这使得零拷贝序列化和反序列化成为可能。

依赖关系

~1.5MB
~35K SLoC