当前位置: 首页 > news >正文

利用Rust与Flutter开发一款小工具

在这里插入图片描述

1.起因

起因是年前看到了一篇Rust + iOS & Android|未入门也能用来造轮子?的文章,作者使用Rust做了个实时查看埋点的工具。其中作者的一段话给了我启发:

无论是 LookinServer 、 Flipper 等 Debug 利器,还是 Flutter / Web Debug Tools,都是在电脑上调试 App。那我们也可以用类似的方式,把实时埋点数据显示在电脑上,不再局限于同一块屏幕。

我司目前的埋点走查是在测试盒子中有一个埋点查看页面,Debug包在数据上报的同时会将信息临时保存起来。当进入这个页面时会以列表的形式展示出来。并且iOS 和Android的页面展示和使用方式也略有不同。

后面我觉得这样进入退出页面查看不方便,就将页面改成了悬浮窗。虽然方便了一些,但是也发现了新的问题:

  • 手机上屏幕大小有限,悬浮窗只有屏幕的一半,可展示信息有限。
  • 悬浮窗会遮挡页面,有时不便于点击页面上的按钮。

刚好前阵子升级了手机系统到Android 13,发现log在控制台都打印不出来了(后面发现App适配到13就正常了。。)。所以有了一个想法,使用Rust通过WebSocket进行数据发送,使用Flutter实现服务端接收App发送的信息并显示出来。

当然了,如果我们的应用是flutter写的,可以直接使用Dart的ffi来直接调用Rust函数。这个我后面有时间会单独写一篇来分享。

2.实现

之所以选择RustFlutter是看中它们的跨平台能力。使用Rust进行WebSocket数据发送,就不用Android和iOS端去重复开发这个功能,只需要简单调用即可,并且Rust有许多开箱即用的库。

Flutter的跨平台能力就更不用说了。比如这个小工具我就可以一套代码输出Windows和macOS两个平台的安装包,保证接收端逻辑和UI的一致。

发送端

Rust部分

关于Rust库的打包以及双端的使用可以看我上一篇分享的Rust库交叉编译以及在Android与iOS使用。这里主要说一下具体的实现代码。

首先是添加WebSocket 库 ws-rs依赖到Cargo.toml文件:

[dependencies]
ws = "0.9.2"
# 全局的静态变量
lazy_static = "1.4.0"

实现代码如下:

use std::collections::HashMap;
use std::sync::Mutex;
use std::{ffi::CStr, os::raw::c_char};
use ws::{connect, Handler, Sender, Handshake, Result, Message, CloseCode, Error};
use ws::util::Token;
#[macro_use]
extern crate lazy_static;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().unwrap();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);}}#[no_mangle]
pub extern "C" fn websocket_connect(host: *const c_char) {let c_host = unsafe { CStr::from_ptr(host) }.to_str().unwrap();if let Err(err) = connect(c_host, |out| {Client {sender: out,host: c_host.to_string(),}}) {println!("Failed to create WebSocket due to: {:?}", err);}
}#[no_mangle]
pub extern "C" fn send_message(host: *const c_char, message: *const c_char) {let c_message = unsafe { CStr::from_ptr(message) }.to_str().unwrap();let c_host = unsafe { CStr::from_ptr(host) }.to_str().unwrap();let binding = DATA_MAP.lock().unwrap();let sender = binding.get(&c_host.to_string());match sender {Some(s) => {if s.send(c_message).is_err() {println!("Websocket couldn't queue an initial message.")};} ,None => println!("None")}
}#[no_mangle]
pub extern "C" fn websocket_disconnect(host: *const c_char) {let c_host = unsafe { CStr::from_ptr(host) }.to_str().unwrap();DATA_MAP.lock().unwrap().remove(&c_host.to_string());
}

简单实现了连接,发送,断开连接三个方法。思路是连接成功后会将发送结构体(Sender)保存在Map中,每次发送时先检查是否连接再发送。这样也就实现了连接多台设备,一对多发送的功能。

Android还需要添加对应的JNI方法:

#[cfg(target_os = "android")]
#[allow(non_snake_case)]
pub mod android {extern crate jni;use self::jni::objects::{JClass, JString};use self::jni::JNIEnv;use super::*;#[no_mangle]pub unsafe extern "C" fn Java_com_weilu_utils_EventLogUtils_sendMessage(env: JNIEnv,_: JClass,host: JString,message: JString,) {send_message(env.get_string(host).expect("invalid pattern string").as_ptr(),env.get_string(message).expect("invalid pattern string").as_ptr(),);}#[no_mangle]pub unsafe extern "C" fn Java_com_weilu_utils_EventLogUtils_connect(env: JNIEnv,_: JClass,host: JString,) {websocket_connect(env.get_string(host).expect("invalid pattern string").as_ptr(),);}#[no_mangle]pub unsafe extern "C" fn Java_com_weilu_utils_EventLogUtils_disconnect(env: JNIEnv,_: JClass,host: JString,) {websocket_disconnect( env.get_string(host).expect("invalid pattern string").as_ptr(),);}
}

至此,发送端部分完成。打包集成进项目就可以使用了。

Android部分

Android端调用代码如下:

public class EventLogUtils {static {System.loadLibrary("event_log_kit");}private static native void sendMessage(final String host, final String message);private static native void connect(final String host);private static native void disconnect(final String host);private static List<String> addressList = null;public static List<String> getAddressList() {return addressList;}/*** 保存 IP 地址,传空时断开所有连接*/public static void saveAddress(String address) {if (TextUtils.isEmpty(address)) {if (addressList != null) {for (String url : addressList) {disconnect(url);}}addressList = null;return;}// 多个地址逗号隔开if (address.contains(",")) {addressList = new ArrayList<>(Arrays.asList(address.split(",")));} else {addressList = new ArrayList<>();addressList.add(address);}for (String url : addressList) {// 子线程调用,可替换为其他方案,这里使用了线程池Executor.getExecutor().getExecutorService().submit(new Runnable() {@Overridepublic void run() {// 循环,如果意外断开,自动重连while (addressList != null) {connect("ws://" + url);}// 工具连接彻底断开}});}}/*** 发送信息*/public static void sendMessage(String message) {if (addressList == null) {return;}for (String url : addressList) {sendMessage("ws://" + url, message);}}
}

代码也比较简单,连接方法在子线程调用,如果发现连接断开会自动重连。

iOS部分就不具体说明了,实现思路一样的。

接收端

首先是发送数据的定义,发送的是json格式字符串。定义的主要参数如下:

class EventLogEntity {/// event/logString type = '';/// 事件名称或log tagString? name;/// 手机型号String? deviceModel;/// 时间戳int time = 0;String data = '';...
}
  • type:用于区分数据类型,目前分为埋点事件与log。
  • name:事件名称或log tag,用于数据的筛选。
  • deviceModel:设备名用于区分数据来源,如果有多个设备同时发送数据可以便于分类。
  • time:时间戳,用于数据排序。

其他参数可以根据自己的需求添加,比如log的等级,数据展示时展开或者收起。

UI组件我使用了fluent_ui,它提供了原生Windows应用风格的组件,比较适合桌面端程序。状态管理使用flutter_riverpod。

具体的代码实现就不多说了,主要说一下核心的数据接收部分。

// https://doc.xuwenliang.com/docs/dart-flutter/2499
class WebSocketManager{HttpServer? requestServer;Future startWebSocketListen() async {final String ip = '192.168.31.232';final String port = '51203';stopWebSocketListen();//HttpServer.bind(主机地址,端口号)requestServer = await HttpServer.bind(ip, int.parse(port)).catchError((error) {debugPrint('bind error: $error');});await for(HttpRequest request in requestServer!) {serveRequest(request).catchError((error){debugPrint('listen error: $error');});}}void stopWebSocketListen() {requestServer?.close();requestServer = null;}Future serveRequest(HttpRequest request) {//判断当前请求是否可以升级为WebSocketif (WebSocketTransformer.isUpgradeRequest(request)) {//升级为webSocketreturn WebSocketTransformer.upgrade(request).then((webSocket) {//webSocket消息监听webSocket.listen((msg) async {debugPrint('listen:$msg');if (webSocket.closeCode == null) {// 这里可以回复客户端消息webSocket.add('收到');}// 可以在这里解析数据,刷新页面...});});} else {return Future((){});}}
}

然后为了便于使用,避免使用者自己查询填写ip,我们需要获取当前设备ip地址:

  Future<String> getDeviceIp() async {String ip = "";if (!kIsWeb) {for (var interface in await NetworkInterface.list()) {for (var address in interface.addresses) {ip = address.address;}}}return ip;}

端口可以给个默认值或者自己随便输入一个,然后可以用shared_preferences插件保存用户配置。下次启动时就自动连接了。
请添加图片描述
手机端可以实现一个输入连接地址的页面,输入电脑端的ip和端口号后就可以发送数据了。或者扫描二维码连接。

3.成果展示

目前实现功能如下:

  • 可同时接收多台设备发送数据,数据按机型名称分类展示。
  • 数据的筛选,搜索(关键字高亮)。
  • 搜索记录的保存。
  • json数据格式化展示。

请添加图片描述


因为小工具在公司内部使用,所以就不开源完整的代码了。有了文章中的核心代码,你可以根据自己的需求实现。也不必局限于这些功能,你完全可以通过Rust和Flutter的跨平台能力开发更多功能,本篇也只是抛砖引玉。

如果本篇对你有所启发帮助,不妨点赞支持一下。如果你有好的想法,也欢迎评论交流。

4.参考

  • Rust + iOS & Android|未入门也能用来造轮子?

相关文章:

利用Rust与Flutter开发一款小工具

1.起因 起因是年前看到了一篇Rust iOS & Android&#xff5c;未入门也能用来造轮子&#xff1f;的文章&#xff0c;作者使用Rust做了个实时查看埋点的工具。其中作者的一段话给了我启发&#xff1a; 无论是 LookinServer 、 Flipper 等 Debug 利器&#xff0c;还是 Flutt…...

零入门kubernetes网络实战-16->使用golang给docker环境下某个容器里添加一个额外的网卡

《零入门kubernetes网络实战》视频专栏地址 https://www.ixigua.com/7193641905282875942 本篇文章视频地址(稍后上传) 上一篇文章&#xff0c;我们使用了golang在veth pair链接的网络命名空间里添加了网卡&#xff0c; 本篇文章&#xff0c;我尝试&#xff0c;在docker环境下…...

音频信号处理笔记(二)

文章目录1.1.3 过零率1.1.4 谱质心和子带带宽1.1.5 短时傅里叶分析法1.1.6 小波变换相关课程&#xff1a; 音频信号处理及深度学习教程傅里叶分析之掐死教程&#xff08;完整版&#xff09;更新于2014.06.06 - 知乎 (zhihu.com)1.1.3 过零率 过零率&#xff1a;是一个信号符号…...

钓鱼网站+bypassuac提权

本实验实现1 &#xff1a;要生成一个钓鱼网址链接&#xff0c;诱导用户点击&#xff0c;实验过程是让win7去点击这个钓鱼网站链接&#xff0c;则会自动打开一个文件共享服务器的文件夹&#xff0c;在这个文件夹里面会有两个文件&#xff0c;当用户分别点击执行后&#xff0c;则…...

合并两个有序链表——递归解法

题目描述21. 合并两个有序链表难度简单2922收藏分享切换为英文接收动态反馈将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a;输入&#xff1a;l1 [1,2,4], l2 [1,3,4]输出&#xff1a;[1,1,2,3,4,4]示例…...

ADRC自抗扰控制总结

目录 前言 1.ADRC形式 1.1形一 1.2形二 2.被控对象 3.仿真分析 3.1仿真模型 3.2仿真结果 4.学习问题 前言 前面的3篇文章依次介绍了微分跟踪器TD、状态观测器ESO和非线性状态误差反馈NLSEF三部分内容&#xff0c;至此ADRC的结构已经介绍完毕&#xff0c;现在对分块学习…...

3年工作之后是不是还在“点点点”,3年感悟和你分享....

经常都有人问我软件测试前景怎么样&#xff0c;每年也都帮助很多朋友做职业分析和学习规划&#xff0c;也很欣慰能够通过自己的努力帮到一些人进入到大厂。 2023年软件测试行业的发展现状以及未来的前景趋势 最近很多测试人在找工作的时候&#xff0c;明显的会发现功能测试很…...

【自动化测试】web自动化测试验证码如何测?如何处理验证码问题?解决方案......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 在对安全性有要求的…...

面试浅谈之 C++ STL 篇

面试浅谈之 C STL 篇 一 &#x1f3e0; 概述 HELLO&#xff0c;各位博友好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 这里是面试浅谈系列&#xff0c;收录在专栏面试中 &#x1f61c;&#x1f61c;&#x1f61c; 本系列将记录一些阿呆个人整理的面试题…...

【PTA Advanced】1144 The Missing Number(C++)

目录 题目 Input Specification: Output Specification: Sample Input: Sample Output: 思路 代码 题目 Given N integers, you are supposed to find the smallest positive integer that is NOT in the given list. Input Specification: Each input file contains…...

oracle的sqlnet.ora文件配置传输加密算法

sqlnet.ora文件位于ORACLE_HOME/network/admin目录中。sqlnet.ora文件中增加如下&#xff1a;SQLNET.ENCRYPTION_SERVER REQUIRED SQLNET.ENCRYPTION_TYPES_SERVER (RC4_256) SQLNET.CRYPTO_CHECKSUM_SERVER REQUIRED SQLNET.CRYPTO_CHECKSUM_TYPES_SERVER MD5SQLNET.ENCRYP…...

RK3568存储性能测试

USBU盘储存性能参数(USB3.0接口)参数测试条件最小典型最大单位说明写速度写入1GB数据—32.6—MB/sU盘型号&#xff1a;KODAK&#xff0c;32GB USB3.0读速度读取1GB数据—66.7—MB/s 备注HW356X-CORE-4GB-32GBHW356X-GKA&#xff0c;操作系统&#xff1a;LinuxU盘储存性能参数(U…...

Homekit智能家居一智能灯泡

一、什么是智能灯 传统的灯泡是通过手动打开和关闭开关来工作。有时&#xff0c;它们可以通过声控、触控、红外等方式进行控制&#xff0c;或者带有调光开关&#xff0c;让用户调暗或调亮灯光。 智能灯泡内置有芯片和通信模块&#xff0c;可与手机、家庭智能助手、或其他智能…...

轻量级 Java 权限认证框架——Sa-Token

文章目录Sa-Token 介绍SpringBoot 集成 Sa-TokenSa-Token 功能登录认证会话查询Token 查询权限认证权限校验角色校验注解鉴权注册 Sa-Token 拦截器关闭注解校验路由拦截鉴权注册 Sa-Token 路由拦截器[记住我] 模式密码加密Sa-Token 集成 Redis方式1、使用 jdk 默认序列化方式方…...

算法复习(四、五、六)

动态规划 动态规划算法的有效性依赖于问题本身所具有的两个重要性质&#xff1a;最优子结构、重叠子问题 关于动态规划算法和备忘录方法的适用条件&#xff1a; 要求&#xff1a; 用分治法和动态规划法分别解决最大子段和问题&#xff08;第四步求最优解不需要掌握&#xff…...

SORT与DeepSORT简介

一、MOT( mutil-object tracking)步骤 在《DEEP LEARNING IN VIDEO MUTIL-OBJECT TEACKING: A SURVEY》这篇基于深度学习多目标跟踪综述中&#xff0c;描绘了MOT问题的四个主要步骤 1.跟定视频原始帧 2.使用目标检测器如Faster-rcnn, YOLO, SSD等进行检测&#xff0c;获取目标…...

TCP/IP网络编程——多播与广播

完整版文章请参考&#xff1a; TCP/IP网络编程完整版文章 文章目录第 14 章 多播与广播14.1 多播14.1.1 多播的数据传输方式以及流量方面的优点14.1.2 路由&#xff08;Routing&#xff09;和 TTL&#xff08;Time to Live,生存时间&#xff09;&#xff0c;以及加入组的办法14…...

K8S DNS解析过程和延迟问题

一、Linux DNS查询解析原理&#xff08;对于调用glibc库函数gethostbyname的程序&#xff09;我们在浏览器访问www.baidu.com这个域名&#xff0c;dns怎么查询到这台主机呢&#xff1f;  1、在浏览器中输入www.baidu.com域名&#xff0c;操作系统会先查找本地DNS解析器缓存&a…...

【JavaScript】js实现深拷贝的方法

前言 在js中我们想要实现深拷贝&#xff0c;首先要了解深浅拷贝的区别。 浅拷贝&#xff1a;只是拷贝数据的内存地址&#xff0c;而不是在内存中重新创建一个一模一样的对象&#xff08;数组&#xff09; 深拷贝&#xff1a;在内存中开辟一个新的存储空间&#xff0c;完完全全…...

RK3288 GPIO记录

1、引脚对应的GPIO 编号第一种 使用/sys/kernel/debug/gpio查询所有gpio引脚的基数第二种 cat /sys/class/gpio/gpiochip248/label对应的label就是GPIO引脚&#xff0c;例如下图GPIO8对应的基数就是2482、计算编号编号 基数 PIN脚如GPIO8的基数是248&#xff0c; GPIO8_A6的编…...

别再为ChatTTS声音飘忽发愁了!手把手教你用Python代码+高质量.pt音色文件,生成稳定语音

用Python和优质音色文件打造稳定语音合成体验 语音合成技术正在改变内容创作的方式&#xff0c;但很多开发者在实际使用ChatTTS时都会遇到一个共同的困扰——生成的语音音色飘忽不定&#xff0c;每次输出都像开盲盒。这种不稳定性严重影响了专业场景下的使用体验&#xff0c;比…...

推荐系统实战:通俗易懂的Apriori关联规则算法

《推荐系统实战&#xff1a;通俗易懂的Apriori关联规则算法》 讲师&#xff1a; [xxxx] 目标 audience&#xff1a; 数据分析师、算法工程师、对推荐系统感兴趣的同学 课时&#xff1a; 1.5 - 2 小时第一部分&#xff1a;引子 —— 从“猜你喜欢”到“买了还买” 1.1 我们熟悉的…...

开源麻将AI分析工具:3步颠覆传统牌局决策模式

开源麻将AI分析工具&#xff1a;3步颠覆传统牌局决策模式 【免费下载链接】Akagi 支持雀魂、天鳳、麻雀一番街、天月麻將&#xff0c;能夠使用自定義的AI模型實時分析對局並給出建議&#xff0c;內建Mortal AI作為示例。 Supports Majsoul, Tenhou, Riichi City, Amatsuki, wit…...

sing-box常见问题排查:99%的用户都会遇到的坑

sing-box常见问题排查&#xff1a;99%的用户都会遇到的坑 引言 sing-box作为一款功能强大的通用代理平台&#xff08;The universal proxy platform&#xff09;&#xff0c;在使用过程中难免会遇到各种问题。本文将针对用户最常遇到的配置错误、连接失败、日志分析等问题提供…...

Swin2SR小白快速上手:无需代码,在线修复低清图片

Swin2SR小白快速上手&#xff1a;无需代码&#xff0c;在线修复低清图片 1. 什么是Swin2SR图像修复技术 Swin2SR是一种基于Swin Transformer架构的AI图像超分辨率技术&#xff0c;它能将低质量图片无损放大4倍。与传统的插值放大方法不同&#xff0c;Swin2SR能够"理解&q…...

网盘直链下载助手:一键获取八大网盘真实下载地址的终极方案

网盘直链下载助手&#xff1a;一键获取八大网盘真实下载地址的终极方案 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 /…...

终极指南:如何使用UABEA高效处理Unity Asset Bundle资源

终极指南&#xff1a;如何使用UABEA高效处理Unity Asset Bundle资源 【免费下载链接】UABEA c# uabe for newer versions of unity 项目地址: https://gitcode.com/gh_mirrors/ua/UABEA UABEA是一款专业的C#资产包提取工具&#xff0c;专门针对新版本Unity引擎的Asset B…...

PaddleOCR模型选型避坑指南:从‘轻量级模型缺失文件’到‘通用模型实战’

PaddleOCR模型选型避坑指南&#xff1a;从轻量级到通用模型的实战解析 第一次接触PaddleOCR时&#xff0c;面对琳琅满目的模型选择&#xff0c;很多开发者都会陷入困惑&#xff1a;轻量级模型和通用模型到底有什么区别&#xff1f;为什么下载的轻量级模型总是提示缺少文件&…...

gte-base-zh WebUI安全加固:禁用CORS、关闭Swagger UI、限制Referer白名单

gte-base-zh WebUI安全加固&#xff1a;禁用CORS、关闭Swagger UI、限制Referer白名单 重要提示&#xff1a;本文介绍的安全加固方案适用于生产环境部署&#xff0c;可有效防止未授权访问和数据泄露风险。 1. 为什么需要WebUI安全加固 当你使用xinference部署gte-base-zh embe…...

hello-uniapp网络状态监听:提升应用健壮性的终极指南

hello-uniapp网络状态监听&#xff1a;提升应用健壮性的终极指南 【免费下载链接】hello-uniapp uni-app框架演示示例 项目地址: https://gitcode.com/gh_mirrors/he/hello-uniapp 在移动应用开发中&#xff0c;网络状态的稳定性直接影响用户体验和应用可靠性。hello-un…...