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

Flutter实现Service + UI 全面跨平台

作者:Karl_wei

前言:

Flutter作为跨平台的UI框架,其可行性已经被市场所认可。UI跨端后,我们自然会希望一些运行在终端的小服务也能跨端,特别是当这个小服务还涉及到一些 UI 的展示。

我们希望Flutter能承担这个角色,让其跨端能力更进一步。

需求背景

我们希望在整机设备上,运行一个后台服务,用户通过ip地址即可调用运行在设备上的能力,同时这个服务还能唤起一些UI视图。
举个例子:假如路由器有Android、windows、mac三个系统的终端,需要提供一个管理后台供用户设置,那么路由器的后台服务能力最好是能够跨这三个系统的。

web后台框架

Dart是支持编写后台服务的,它提供了 shelf 库,以处理HTTP请求。整个项目,我们都是围绕shelf库的能力集进行开发的。

静态资源 → shelf_static

从需求我们可以了解到,我们需要提供给用户一个web管理后台进行管理,web的资源自然是放在服务端的。这里我们使用 shelf_static 库,使用非常的简单,就一个创建静态资源操作器的接口。

import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_static/shelf_static.dart';void main() {var handler = createStaticHandler('example/files',defaultDocument: 'index.html');io.serve(handler, 'localhost', 8080);
}

需要注意的是,必须传入本地的绝对路径,指定默认的文件入口。Flutter中,资源一般以asset的方式导入,在编译过程中以二进制的形式打包在应用中,并不是普通格式的文件,那么如何传入给createStaticHandler?
我们通过AssetBundle获取到这些文件的字节流,并转化成File保存到指定路径,这个路径就是静态资源的路径。

static Future<String> copyAssets() async {int now = DateTime.now().millisecondsSinceEpoch;String folderPath = '/sdcard';final manifestContent = await rootBundle.loadString('AssetManifest.json');final Map<String, dynamic> manifestMap = json.decode(manifestContent);final assetList = manifestMap.keys.where((String key) => key.startsWith('assets/web')).toList();for (final asset in assetList) {await copyAsset(asset, folderPath);}print('移动文件耗时 = ${DateTime.now().millisecondsSinceEpoch - now}毫秒');return '$folderPath/assets/web';
}static Future<File> copyAsset(String assetName, String localPath) async {int lastSeparatorIndex = assetName.lastIndexOf('/');Directory directory = Directory('$localPath${Platform.pathSeparator}${assetName.substring(0, lastSeparatorIndex)}');if (!directory.existsSync()) directory.createSync(recursive: true);ByteData data = await rootBundle.load(assetName);Uint8List bytes = data.buffer.asUint8List();final file = File('$localPath${Platform.pathSeparator}$assetName');await file.writeAsBytes(bytes);return file;
}

调用copyAssets可以拿到路径,整个过程一般不会超过500ms,视文件体积而定。

路由 → shelf_route

现在我们已经可以访问静态资源了,接下来需要提供一系列的接口供前端调用,这个时候我们需要用到 shelf_route 库。
shelf_route 支持 RESTful 风格的路由,可以处理客户端的 GET、POST、PUT、DELETE 等 HTTP 请求,也可以从 HTTP 路径中自动提取参数。每个路由会提供request请求体,最终返回Response的构造函数即可。
用法很简单,下面简单演示下如何编写一个登录接口。

import 'package:shelf_router/shelf_router.dart' as self_router;self_router.Router app = self_router.Router();// TODO:使用mount,前缀使用模块命名
app.post(Apis.login, userLogin);
app.post(Apis.resetPwd, resetPassword);
app.post(Apis.signOut, singOutHandle);

Future<Response> userLogin(Request request) async {final requestBody = await request.readAsString();final Map<String, dynamic> body = json.decode(requestBody);Auth auth = Auth();var info = await auth.getUserInfo();if (info.$1 == body['username'] && info.$2 == body['password']) {String token = await auth.generateToken(body['username'], body['password']);return Response.ok(BaseResponse(Code.success, data: {'token': token}, msg: '登录成功').toString());} else {return Response.ok(BaseResponse(Code.reject, msg: '账号密码错误').toString());}
}

中间件 → helf_multipart

一般后台服务,都需要对部分接口进行鉴权操作,这部分的逻辑一般是通用的,一般开发过程中我们会用到中间件的机制
中间件通常被用于拦截和处理请求与响应之间的过程,以实现一些公共的应用逻辑和功能,比如认证、日志记录、错误处理等等。
在Flutter中,我们使用 shelf_multipart 这个库,通过Pipeline可以加上Middleware,这个中间件是应用于所有路由的,因此某些接口不需要这个中间件操作,直接在白名单内过滤即可;innerHandler则是执行对应的响应操作。

var middleHandler = const Pipeline().addMiddleware(authMiddleware); // 添加中间件

Middleware authMiddleware = (Handler innerHandler) {return (Request request) async {String path = request.url.path.split('?').first;if (!whitelist.contains(path)) { // 过滤白名单String? token = request.headers['Authorization'];Auth auth = Auth();var authVerify = await auth.verifyToken(token); // 验证tokenif (!authVerify.$1) {return Response.unauthorized(BaseResponse(Code.reject, msg: authVerify.$2!).toString());} else {auth.updateTokenTime(); // 有操作则续费token时长}}final response = await innerHandler(request);return response;};
};

websocket → shelf_websocket

上面所写的都是提供HTTP服务的,在业务中也经常存在需要websocket,我们使用 shelf_websocket 库。跟静态资源一样,单一的能力只需要提供最简单的接口:webSocketHandler

import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_web_socket/shelf_web_socket.dart';
import 'package:web_socket_channel/web_socket_channel.dart';void main() {var webSocketHandler = webSocketHandler((webSocket) {webSocket.stream.listen((message) {webSocket.sink.add("echo $message");});});shelf_io.serve(handler, 'localhost', 8080).then((server) {print('Serving at ws://${server.address.host}:${server.port}');});
}

最后我们需要把所有的handler都整合成一个服务,传给io.serve

Handler cascadeHandler = Cascade().add(handler).add(app).add(webSocketHandler).handler; // 合并静态资源、路由、websocket// 合入中间件
// 创建本机服务,端口8888
await io.serve(middleHandler.addHandler(cascadeHandler), '0.0.0.0', 8888);

通用服务能力

用户鉴权

一般这种小型本机服务,登录用户都是互斥的,用户权限管理我们可以简单的使用:hive + JWT token。
采用hive来保存用户信息,通过 dart_jsonwebtoken 库生成token,然后在中间件拦截,对header中携带的token信息进行验证,从而达到鉴权的目的。

Future<String> generateToken(String userName, String password) async {Box box = await Hive.openBox(_boxName);JWT jwt = JWT({'userName': userName,'password': password,},jwtId: const Uuid().v4(),);String token = jwt.sign(SecretKey(_secretKey));await box.put(Constant.userNameKey, userName);await box.put(Constant.pwdKey, password);await box.put(Constant.tokenKey, token);updateTokenTime();return token;
}

文件上传

一般web后台,都会把文件资源存储在另一个文件服务中,比如:七牛云。不过既然是小服务,我们也希望dart能拥有这个能力。
文件上传的路由,参数一般都是form表单;当解析到request为isMultipart时,则对文件流进行读取,并写到本地路径中。
特别需要注意的是:Dart是单线程,写文件这种耗时io操作,必须使用IOSink + stream方式写入,不然内存会拉满,大文件会直接让应用崩溃。

app.post(Apis.upload, uploadFile);Future<Response> uploadFile(Request request) async {if (!request.isMultipart) {return Response.ok('Not a multipart request');} else if (request.isMultipartForm) {String? filename;String? path;await for (var part in request.parts) {var contentDisposition = part.headers['content-disposition'];filename = RegExp(r'filename="([^"]*)"').firstMatch(contentDisposition!)?.group(1);path = '${await CommonUtils.getDownloadPath()}$filename';File? file = File(path);IOSink sink = file.openWrite();await sink.addStream(part);await sink.flush();await sink.close();}return Response.ok(BaseResponse(Code.success, data: {"filePath": path}).toString());} 
}

运行机制:Service + UI

使用Flutter编写这种后台服务,还有一个好处是可以跨平台的展示UI。比如:需要后台弹出一些设置成功的toast,这个时候就非常的方便了。

Android平台,我们在Android Service上创建一个Flutter Engine,可以直接执行到Dart代码;当我们需要展示UI的时候,只需要通过我们的多窗口插件打开一个悬浮窗即可。

Windows平台,我们目前还没有在C++ 服务上运行dart代码,而是通过把窗口设置为0在后台运行着;当需要展示UI的时候,恢复窗口大小,然后进入指定的UI界面即可。

结语

在常规业务场景基本都不会使用dart开发后台服务;针对整机小型服务的需求,我认为Flutter还是挺香的,内存不存在隐患,还能前后端都跨平台。
本篇文章,分享了整个shelf框架编写web服务的经验,我认为在这个小众的类目中这篇文章算是非常齐全了;同时我们也验证了Flutter/Dart在web服务的可行性,Flutter的业务价值进一步提升~

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

相关文章:

Flutter实现Service + UI 全面跨平台

作者&#xff1a;Karl_wei 前言&#xff1a; Flutter作为跨平台的UI框架&#xff0c;其可行性已经被市场所认可。UI跨端后&#xff0c;我们自然会希望一些运行在终端的小服务也能跨端&#xff0c;特别是当这个小服务还涉及到一些 UI 的展示。 我们希望Flutter能承担这个角色&…...

微软商店的ubuntu 连不上网Temporary failure in name resolution

背景&#xff1a;win10 下载docker时需要wsl2&#xff0c;下了个微软商店的Ubuntu 。写这篇文章的原因是当时查了资料ubuntu的问题和微软下载的Ubuntu还是有一些区别&#xff0c;问题不好解决&#xff0c;故写此文。 问题&#xff1a;用命令ifconfig eth0 down后再执行ifconfi…...

“深入剖析JVM内部工作原理:解密Java虚拟机“

标题&#xff1a;深入剖析JVM内部工作原理&#xff1a;解密Java虚拟机 摘要&#xff1a; 本文将深入剖析Java虚拟机&#xff08;JVM&#xff09;的内部工作原理&#xff0c;包括类加载、运行时数据区、垃圾回收、即时编译等关键概念和机制。通过对JVM的解密&#xff0c;我们将…...

数据结构与算法基础

一、基本概念和术语 &#xff08;一&#xff09;数据元素、数据结构、抽象数据类型等概念 &#xff08;二&#xff09;算法设计的基本要求 &#xff08;三&#xff09;语句的频度和估算时间复杂度 二、线性表 &#xff08;一&#xff09;线性表的定义和基本操作 &#xff08…...

人工智能任务1-【NLP系列】句子嵌入的应用与多模型实现方式

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能任务1-【NLP系列】句子嵌入的应用与多模型实现方式。句子嵌入是将句子映射到一个固定维度的向量表示形式&#xff0c;它在自然语言处理&#xff08;NLP&#xff09;中有着广泛的应用。通过将句子转化为向量…...

【Java并发编程面试题(60道)】

toc Java并发编程面试题(60道) 基础 1.并行跟并发有什么区别&#xff1f; 从操作系统的角度来看&#xff0c;线程是CPU分配的最小单位。 并行就是同一时刻&#xff0c;两个线程都在执行。这就要求有两个CPU去分别执行两个线程。并发就是同一时刻&#xff0c;只有一个执行&…...

Python:逢七拍腿游戏

场景模拟&#xff1a; 通过在 for 循环中使用 continue 语句实现计算拍腿次数&#xff0c;即计算从1到100&#xff08;不包括100&#xff09;&#xff0c;一共有多少个尾数为7或7的倍数这样的游戏&#xff0c;代码如下&#xff1a; total 99 # 记…...

esp32C3 micropython oled 恐龙快跑游戏

目录 简介 效果展示 源代码 main.py ssd1306.py 实现思路 血量值 分数 恐龙 障碍物 得分与血量值的计算 简介 使用合宙esp32c3模块&#xff0c;基于micropython平台开发的一款oled小游戏&#xff0c;恐龙快跑&#xff0c;所有代码已经给出&#xff0c;将两个py文件…...

53.Linux day03 文件查看命令,vi/vim常用命令

今天进行了新的学习。 目录 1.cat a.查看单个文件的内容&#xff1a; b.查看多个文件的内容&#xff1a; c.将多个文件的内容连接并输出到一个新文件&#xff1a; d.显示带有行号的文件内容&#xff1a; 2.more 3.less 4.head 5.tail 6.命令模式 7.插入模式 8.图…...

YOLOv8改进后效果

数据集 自建铁路障碍数据集-包含路障&#xff0c;人等少数标签。其中百分之八十作为训练集&#xff0c;百分之二十作为测试集 第一次部署 版本&#xff1a;YOLOv5 训练50epoch后精度可达0.94 mAP可达0.95.此时未包含任何改进操作 第二次部署 版本&#xff1a;YOLOv8改进版本 首…...

小程序的数据绑定和事件绑定

小程序的数据绑定 1.需要渲染的数据放在index.js中的data里 Page({data: {info:HELLO WORLD,imgSrc:/images/1.jpg,randomNum:Math.random()*10,randomNum1:Math.random().toFixed(2)}, }) 2.在WXML中通过{{}}获取数据 <view>{{info}}</view><image src"{{…...

第四章MyBatis核心配置文件

environments与environment标签 environments主要用来配置环境&#xff0c;属性default表示默认环境&#xff0c;值为environment的idenvironment为具体环境&#xff0c;属性id表示环境唯一标识environments可以有多个environment 加载默认环境 sqlSessionFactory sqlSessi…...

⛳ Docker - Centos 安装配置

目录 ⛳ Docker - Centos 安装配置&#x1f3ed; Docker 安装&#xff1a;&#x1f4e2; 一、安装依赖包&#x1f4ac; 二、添加 Docker 下载源地址&#x1f43e; 三、更新yum缓存&#x1f463; 四、安装Docker&#x1f4bb; 五、启动Docker&#x1f381; 六、查看Docker状态和…...

Python web实战之Django 的跨站点请求伪造(CSRF)保护详解

关键词&#xff1a;Python、Web、Django、跨站请求伪造、CSRF 大家好&#xff0c;今天我将分享web关于安全的话题&#xff1a;Django 的跨站点请求伪造&#xff08;CSRF&#xff09;保护&#xff0c;介绍 CSRF 的概念、原理和保护方法. 1. CSRF 是什么&#xff1f; CSRF&#…...

ARM(汇编指令)

.global _start _start:/*mov r0,#0x5mov r1,#0x6 bl LoopLoop:cmp r0,r1beq stopsubhi r0,r0,r1subcc r1,r1,r0mov pc,lr*/ mov r0,#0x1mov r1,#0x0mov r2,#0x64bl Loop Loop:cmp r0,r2bhi stopadd r1,r1,r0add r0,r0,#0x01mov pc,lr stop:B stop.end...

神经网络基础-神经网络补充概念-01-二分分类

概念 二分分类是一种常见的机器学习任务&#xff0c;其目标是将一组数据点分成两个不同的类别。在二分分类中&#xff0c;每个数据点都有一个与之关联的标签&#xff0c;通常是“正类”或“负类”。算法的任务是根据数据点的特征来学习一个模型&#xff0c;以便能够准确地将新…...

Linux16(1) 线程同步

目录 1、概念 2、线程的实现&#xff1a; 3、线程同步&#xff1a; 4、使用信号量&#xff1a; 5、使用信号量实现进程同步&#xff1a; 6、使用互斥锁 7、使用互斥锁实现线程同步 8、读写锁 9、使用读写锁 10、使用读写锁实现进程同步 1、概念 线程&#xff1a;进程…...

深入探讨lowess算法:纯C++实现与局部加权多项式回归的数据平滑技术

引言 在统计学和数据科学中&#xff0c;有时我们面对的数据是嘈杂的、充满噪声的。为了更好地揭示数据的潜在趋势和结构&#xff0c;数据平滑技术成为了一个重要工具。lowess或称为局部加权多项式回归是其中的一种流行方法&#xff0c;它对每一个点给予一个权重&#xff0c;根…...

Sui安全篇|详解零知识证明 (ZKP) Groth16的可塑性

Sui Move允许用户使用Groth16进行高效验证任何非确定性多项式时间&#xff08;Non-deterministic Polynomial time &#xff0c;NP&#xff09;状态。Groth16是一种高效且广泛使用的零知识简洁非交互知识证明&#xff08;Zero-Knowledge Succinct Non-interactive Argument of …...

记录--webpack和vite原理

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 每次用vite创建项目秒建好&#xff0c;前几天用vue-cli创建了一个项目&#xff0c;足足等了我一分钟&#xff0c;那为什么用 vite 比 webpack 要快呢&#xff0c;这篇文章带你梳理清楚它们的原理…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...