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 要快呢,这篇文章带你梳理清楚它们的原理…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
