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 全面跨平台
作者:Karl_wei 前言: Flutter作为跨平台的UI框架,其可行性已经被市场所认可。UI跨端后,我们自然会希望一些运行在终端的小服务也能跨端,特别是当这个小服务还涉及到一些 UI 的展示。 我们希望Flutter能承担这个角色&…...
微软商店的ubuntu 连不上网Temporary failure in name resolution
背景:win10 下载docker时需要wsl2,下了个微软商店的Ubuntu 。写这篇文章的原因是当时查了资料ubuntu的问题和微软下载的Ubuntu还是有一些区别,问题不好解决,故写此文。 问题:用命令ifconfig eth0 down后再执行ifconfi…...
“深入剖析JVM内部工作原理:解密Java虚拟机“
标题:深入剖析JVM内部工作原理:解密Java虚拟机 摘要: 本文将深入剖析Java虚拟机(JVM)的内部工作原理,包括类加载、运行时数据区、垃圾回收、即时编译等关键概念和机制。通过对JVM的解密,我们将…...
数据结构与算法基础
一、基本概念和术语 (一)数据元素、数据结构、抽象数据类型等概念 (二)算法设计的基本要求 (三)语句的频度和估算时间复杂度 二、线性表 (一)线性表的定义和基本操作 (…...
人工智能任务1-【NLP系列】句子嵌入的应用与多模型实现方式
大家好,我是微学AI,今天给大家介绍一下人工智能任务1-【NLP系列】句子嵌入的应用与多模型实现方式。句子嵌入是将句子映射到一个固定维度的向量表示形式,它在自然语言处理(NLP)中有着广泛的应用。通过将句子转化为向量…...
【Java并发编程面试题(60道)】
toc Java并发编程面试题(60道) 基础 1.并行跟并发有什么区别? 从操作系统的角度来看,线程是CPU分配的最小单位。 并行就是同一时刻,两个线程都在执行。这就要求有两个CPU去分别执行两个线程。并发就是同一时刻,只有一个执行&…...
Python:逢七拍腿游戏
场景模拟: 通过在 for 循环中使用 continue 语句实现计算拍腿次数,即计算从1到100(不包括100),一共有多少个尾数为7或7的倍数这样的游戏,代码如下: total 99 # 记…...
esp32C3 micropython oled 恐龙快跑游戏
目录 简介 效果展示 源代码 main.py ssd1306.py 实现思路 血量值 分数 恐龙 障碍物 得分与血量值的计算 简介 使用合宙esp32c3模块,基于micropython平台开发的一款oled小游戏,恐龙快跑,所有代码已经给出,将两个py文件…...
53.Linux day03 文件查看命令,vi/vim常用命令
今天进行了新的学习。 目录 1.cat a.查看单个文件的内容: b.查看多个文件的内容: c.将多个文件的内容连接并输出到一个新文件: d.显示带有行号的文件内容: 2.more 3.less 4.head 5.tail 6.命令模式 7.插入模式 8.图…...
YOLOv8改进后效果
数据集 自建铁路障碍数据集-包含路障,人等少数标签。其中百分之八十作为训练集,百分之二十作为测试集 第一次部署 版本:YOLOv5 训练50epoch后精度可达0.94 mAP可达0.95.此时未包含任何改进操作 第二次部署 版本: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主要用来配置环境,属性default表示默认环境,值为environment的idenvironment为具体环境,属性id表示环境唯一标识environments可以有多个environment 加载默认环境 sqlSessionFactory sqlSessi…...
⛳ Docker - Centos 安装配置
目录 ⛳ Docker - Centos 安装配置🏭 Docker 安装:📢 一、安装依赖包💬 二、添加 Docker 下载源地址🐾 三、更新yum缓存👣 四、安装Docker💻 五、启动Docker🎁 六、查看Docker状态和…...
Python web实战之Django 的跨站点请求伪造(CSRF)保护详解
关键词:Python、Web、Django、跨站请求伪造、CSRF 大家好,今天我将分享web关于安全的话题:Django 的跨站点请求伪造(CSRF)保护,介绍 CSRF 的概念、原理和保护方法. 1. CSRF 是什么? 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-二分分类
概念 二分分类是一种常见的机器学习任务,其目标是将一组数据点分成两个不同的类别。在二分分类中,每个数据点都有一个与之关联的标签,通常是“正类”或“负类”。算法的任务是根据数据点的特征来学习一个模型,以便能够准确地将新…...
Linux16(1) 线程同步
目录 1、概念 2、线程的实现: 3、线程同步: 4、使用信号量: 5、使用信号量实现进程同步: 6、使用互斥锁 7、使用互斥锁实现线程同步 8、读写锁 9、使用读写锁 10、使用读写锁实现进程同步 1、概念 线程:进程…...
深入探讨lowess算法:纯C++实现与局部加权多项式回归的数据平滑技术
引言 在统计学和数据科学中,有时我们面对的数据是嘈杂的、充满噪声的。为了更好地揭示数据的潜在趋势和结构,数据平滑技术成为了一个重要工具。lowess或称为局部加权多项式回归是其中的一种流行方法,它对每一个点给予一个权重,根…...
Sui安全篇|详解零知识证明 (ZKP) Groth16的可塑性
Sui Move允许用户使用Groth16进行高效验证任何非确定性多项式时间(Non-deterministic Polynomial time ,NP)状态。Groth16是一种高效且广泛使用的零知识简洁非交互知识证明(Zero-Knowledge Succinct Non-interactive Argument of …...
记录--webpack和vite原理
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前言 每次用vite创建项目秒建好,前几天用vue-cli创建了一个项目,足足等了我一分钟,那为什么用 vite 比 webpack 要快呢,这篇文章带你梳理清楚它们的原理…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...
6.9-QT模拟计算器
源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...
Java并发编程实战 Day 11:并发设计模式
【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天,今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案,它们不仅提供了优雅的设计思路,还能显著提升系统的性能…...
DAY 45 超大力王爱学Python
来自超大力王的友情提示:在用tensordoard的时候一定一定要用绝对位置,例如:tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾: tensorboard的发展历史和原理tens…...
C++11 constexpr和字面类型:从入门到精通
文章目录 引言一、constexpr的基本概念与使用1.1 constexpr的定义与作用1.2 constexpr变量1.3 constexpr函数1.4 constexpr在类构造函数中的应用1.5 constexpr的优势 二、字面类型的基本概念与使用2.1 字面类型的定义与作用2.2 字面类型的应用场景2.2.1 常量定义2.2.2 模板参数…...
