Flutter调用Rust代码操作指南

news2024/11/20 14:27:37

在之前的利用Rust与Flutter开发一款小工具文章中,我们使用Rust代码实现了一个简单的WebSocket发送功能。也在Rust库交叉编译以及在Android与iOS使用这篇中介绍了Rust库的打包以及双端的使用。

今天我们继续用之前WebSocket的代码举例,来介绍如何在Flutter项目中使用。

1.准备工作

本篇的主角就是flutter_rust_bridge,它是用于 FlutterRust 的高级内存安全绑定生成器。这个库只是一个代码生成器,帮助你的 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 代码/
  • 安装 LLVM, 请看 Installing LLVM,ffigen 会使用到。
  • (可选) 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 不会将符号去除。

2.Android配置

首先安装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每次运行都会打包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项目目录下执行cargo xcode。执行后,会生成一个xcodeproj后缀文件夹。它可以用于导入到其他 Xcode 项目中。
请添加图片描述
在 Xcode 中打开 ios/Runner.xcodeproj, 点击菜单File ---> Add Files to "Runner"接着把 xxx.xcodeproj 添加为子项目。

  • 点击 Runner 根项目,在Build PhasesTab下的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)
  }
}

Flutter调用

至此,配置工作已全部结束。下面我们看下如何调用,首先我们简单封装一个方法调用类。

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,有需要的可以运行查看。对你有帮助的话,点赞收藏起来~我们下个月再见!

参考

  • Flutter和Rust如何优雅的交互

  • flutter_rust_bridge中文版文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/633033.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python怎么取出换行的数据?

Python怎么取出换行的数据&#xff1f; 在数据分析和处理中&#xff0c;我们常常需要从文件中读取数据&#xff0c;特别是当数据量很大时更是如此。在Python中&#xff0c;读取文本文件中的数据很简单&#xff0c;但是有时候读取的文本文件中可能含有换行符&#xff0c;这可能…

5.DIY可视化-拖拽设计1天搞定主流小程序-公告管理

1.DIY可视化-拖拽设计1天搞定主流小程序-公告管理 公告管理-本教程均在第一节中项目启动下操作 1.DIY可视化-拖拽设计1天搞定主流小程序-公告管理2.创建数据表执行: php think diygw:tableandapi oneHdp 3. 复制幻灯片界面进行编辑4.修改后台API5.富文本添加6.验证 :访问url : …

linux下动态链接过程

序 总是在工作中会遇到符号表&#xff0c;链接等字眼&#xff0c;之前看过《程序员自我修养》这本书&#xff0c;但是基本上都忘记了&#xff0c;这几天再刷一遍&#xff0c;顺便记录一下&#xff0c;加深记忆。 本文会完整的描述程序运行的动态加载及运行的整个流程&#xff…

大学生心理健康APP的设计与实现

摘 要&#xff1a;大学生群体本该是象牙塔中的学习者&#xff0c;是祖国未来的支柱&#xff0c;是民族复兴的期望所在&#xff0c;但是近几年大学生心理健康问题日益严峻&#xff0c;例如情感问题&#xff0c;原生家庭的问题&#xff0c;学业问题&#xff0c;与同学相处所带来…

【ps】photoshop 入门

文章目录 Adobe Photoshop 安装快捷键照片裁剪保存/导出图片复制图层新建图层及颜色填充人物轮廓调整、增高颅顶人物瘦脸、缩小鼻翼、丰唇、放大眼睛人物祛痘/祛斑背景杂质去除图像提亮调色工具 Adobe Photoshop 安装 参照 【Adobe Photoshop 2023 v24.3.0【附安装破解教程】中…

串口通讯,三种数据传输方式介绍

前言 &#xff08;1&#xff09;备考微机原理&#xff0c;这里就把这个知识点分享给和我一样不认真上课的同学们。 &#xff08;2&#xff09;虽然在我的STM32串口通讯的教程里面已经有描述这部分的内容了。但是担心有些人只是想单纯的了解这个知识点&#xff0c;所以我就又单独…

【工具】putty下载、安装、使用和常用设置教程

一&#xff0c;Putty简介 Putty是一个远程登录工具&#xff0c;适用于 Windows 和 Unix 平台的Telnet、SSH和 RLOGIN客户端。 跨平台支持&#xff1a;PuTTY 可以在多个操作系统上运行&#xff0c;包括 Windows、Linux、Mac 等&#xff0c;使得在不同的平台上使用相同的工具成为…

YOLOV1——你总能在这找到你想要的答案

目录 一&#xff1a;前言 与传统的RCNN的区别在哪&#xff1f; 二&#xff1a;YOLO-V1中心思想 三&#xff1a;计算损失 &#xff08;1&#xff09;&#xff1a;位置损失 为什么高宽要加上根号的损失&#xff1f; &#xff08;2&#xff09;&#xff1a;置信度损失 &a…

一次虚拟机centos7系统崩溃恢复记录

前两天&#xff0c;因为单独断开电源测试网络&#xff0c;结果不知道为什么&#xff0c;两台虚拟机中一台就出现紧急状态&#xff0c;看日志是下面情况 通过网上一些资料&#xff0c;说可以通过xfs_repair -v -L /dev/dm-0来修复&#xff0c;但这次我这里没有相关 看这个mappe…

【Docker】docker部署前Springboot-vue后端分离项目【实战篇】

文章目录 1、docker 安装jdk2、docker 安装mysql通过Docker命令进入Mysql容器内部初始化数据sqlDbx连接查看 3、docker build构建后端镜像修改配置数据库JDBC链接IP为虚拟机服务器IPmaven clean package打包后端jar并上传到服务器编写Dockfilebuild 构建查看构建的后端镜像app …

AAOS系列之音量设置

文章目录 前言UI 处理流程音量键的响应流程AudioService设置音量流程AAOS音量条选项总结 前言 AAOS音量设置内容可以分为几个部分 音量的ui响应流程legacy模式dynamic模式通过按键进行设置 AAOS 音量设置比通用的Android系统复杂&#xff0c;表现在两个方面。首先是UI响应上…

chatgpt赋能python:如何取出带有4的整数

如何取出带有4的整数 Python是一门功能强大的编程语言&#xff0c;可以轻松解决复杂的编程问题。在本文中&#xff0c;我们将介绍如何使用Python编程语言从一个整数列表中取出所有带有4的整数。我们将从介绍如何创建一个整数列表开始&#xff0c;然后编写Python代码以实现我们…

Unreal 5 实现丢弃武器功能

之前实现了物品的拾取功能&#xff0c;但是没有实现丢弃功能&#xff0c;这一篇讲一下我是如何实现丢弃功能的。 丢弃功能的逻辑是&#xff1a;拾取物品时&#xff0c;需要使用拾取的物品进行类的实现&#xff0c;所以&#xff0c;我们需要把数据存储到拾取的物品上&#xff0c…

4.DIY可视化-拖拽设计1天搞定主流小程序-后台设计幻灯

后台设计幻灯:本教程均在第一节中项目启动下操作 1.首页幻灯片php安装:phpthink安装&#xff1a; 2.数据库创建&#xff1a;幻灯片表&#xff1a;项目根目录下cmd :执行 : php think diygw:tableandapi oneHdp查看结果目录:访问url: http://one.com/one/api.hdp/list 3.界面增删…

【6.10 代随_53day】 最长公共子序列、不相交的线、最大子数组和

最长公共子序列、不相交的线、最大子数组和 最长公共子序列动态规划方法图解步骤代码 不相交的线动态规划方法 最大子数组和图解步骤代码 最长公共子序列 力扣连接&#xff1a;1143. 最长公共子序列&#xff08;中等&#xff09; 动态规划方法 确定递推公式 主要就是两大情况…

《横向联邦学习中 PCA差分隐私数据发布算法》论文算法原理笔记

论文地址&#xff1a;https://www.arocmag.com/article/01-2022-01-041.html 论文摘要 为了让不同组织在保护本地敏感数据和降维后发布数据隐私的前提下&#xff0c;联合使用 PCA进行降维和数据发布&#xff0c;提出横向联邦 PCA差分隐私数据发布算法。引入随机种子联合协商方…

九、进程程序替换

文章目录 一、进程程序替换&#xff08;一&#xff09;概念&#xff08;二&#xff09;为什么程序替换&#xff08;三&#xff09;程序替换的原理&#xff08;四&#xff09;如何进行程序替换1. execl2. 引入进程创建——子进程执行程序替换&#xff0c;会不会影响父进程呢? &…

016、元组(行)结构与dml操作

元组结构与dml操作 元组(行)结构行头信息Pageinspect工具DML操作InsertionDeletionUpdate块空间清理元组(行)结构 t_xmin保存插入此元组的事务的txid。这一行被哪个事务ID所修改t_xmax保存删除或更新此元组的事务的txid。如果此元组未被删除或更新,则t_xmax设置为0,这意味…

数据结构——堆(C语言实现)

文章目录 什么是堆堆的实现堆的结构定义堆的初始化接口堆的销毁接口堆的插入数据接口向上调整建堆接口判断堆是否为空堆的删除数据接口向下调整建堆接口获取堆顶数据获取堆的有效数据个数完整实现代码小结 堆排序堆排序的实现 关于建堆和堆排序时间复杂度的分析向下调整建堆向上…

STL之string

目录 string的基本实现一. string的基本架构二. 常用方法及实现1.初始化和清理a. 构造函数b. 析构函数c. swapd. 拷贝构造e. operator 2.成员访问a. operator[]b. sizec. c_str 3.迭代器iteratora. beginb. end 4.增删查改a. reserveb. resizec. insertd. push_backe. appendf.…