面客户端软件开发框架是用于创建桌面应用程序的工具集合,它们提供了开发者需要的基本组件、库和工具,以便于快速构建功能丰富、可靠的桌面应用程序。以下是一些常用的桌面客户端软件开发框架,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。
1.Electron:Electron 是一个开源的跨平台框架,使用 HTML、CSS 和 JavaScript 构建桌面应用程序。它基于 Chromium 和 Node.js,支持 Windows、macOS 和 Linux 等多个平台。Electron 由 GitHub 开发,许多知名应用如 Slack、Visual Studio Code 和 Discord 都是使用 Electron 构建的。
2.Qt:Qt 是一个跨平台的 C++ 应用程序开发框架,提供了丰富的 GUI 组件和工具包,可用于构建高性能的桌面应用程序。Qt 支持 Windows、macOS、Linux 等主流操作系统,以及移动平台如 Android 和 iOS。它被广泛应用于各种行业领域,包括汽车、航空航天、医疗等。
3.JavaFX:JavaFX 是 Java 平台的图形界面框架,用于构建丰富的交互式桌面应用程序。JavaFX 提供了丰富的 UI 控件、动画效果和多媒体支持,并与 Java 语言紧密集成。JavaFX 可以在 Windows、macOS 和 Linux 等操作系统上运行。
4.GTK+:GTK+ 是一个跨平台的 GUI 工具包,使用 C 语言编写,提供了丰富的界面组件和工具,适用于构建 GNOME 桌面环境下的应用程序。GTK+ 支持 Linux、Windows 和 macOS 等操作系统。
5.WPF (Windows Presentation Foundation):WPF 是 Microsoft .NET Framework 的一部分,用于构建 Windows 平台上的富客户端应用程序。WPF 提供了 XAML 标记语言来定义用户界面,以及强大的数据绑定和样式化功能。
6.Cocoa:Cocoa 是 macOS 平台上的应用程序开发框架,使用 Objective-C 或 Swift 编程语言。它提供了丰富的 API 和工具,用于构建 macOS 和 iOS 应用程序,并与 macOS 操作系统紧密集成。
7.Avalonia:Avalonia 是一个跨平台的 .NET GUI 框架,使用 C# 语言编写,可用于构建 Windows、macOS 和 Linux 上的桌面应用程序。Avalonia 的设计受到 WPF 和 Xamarin.Forms 的启发,提供了 XAML 标记语言和 MVVM 模式支持。
选择桌面客户端软件开发框架时,通常需要考虑开发者的技能水平、项目需求、目标平台以及性能要求等因素。
骑士是哈啰的一款终端安全应用,本文主要介绍我们在做新版哈骑士桌面端时的一些技术架构思考和实践,分享我们沉淀的一些桌面端应用的解决方案和经验。
为什么选择Electron
前端开发者入门快
Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux——不需要本地开发经验,有了它,前端开发者就可以使用前端开发技术来开发桌面应用了。
支持跨端&开发效率高
如上图所示:
作为一个跨平台的桌面应用开发框架,Electron 的迷人之处在于,它是建立在 Chromium 和 Node.js 之上的,二位分工明确,一个负责界面,一个负责背后的逻辑。虽然系统间还是会有很大的差异,需要相应地做一些额外处理,使得打包出的应用在不同系统下都能正常运转,但相比于 80% 都能完全复用的代码,这些时间和成本都是可以忽略的,开发效率直接翻倍,如果你开发一个不需要太关注底层的桌面端应用,基本不需要做底层的抹平逻辑。
另外,Electron 是基于 Node.js 的,这就意味着,Node 这个大生态下的模块,Electron 都可以用。同时,跨平台也让 Electron 可同时开发 Web 应用和桌面应用,无论是 UI,还是代码,很多资源都可以共享,大幅减少了开发者的工作量。
生态繁荣&案例成熟
Electron生态的确很强大,各种库和工具包都为你构建一个桌面端应用提供了很多方案。
当然,不止如此,现在用Electron做桌面端的案例也非常成熟了。上图已经说明了Electron应用是有多广泛了,这其中不乏大名鼎鼎、如雷贯耳的应用,例如 Postman、Skype、VScode 等。而且我敢打赌,各位看官的电脑上一定安装过用 Electron 开发的应用,如果你用的是 Mac 电脑,请在命令行运行下面的命令来检测本地采用 Electron 技术开发的桌面软件:
for app in /Applications/*; do;[ -d $app/Contents/Frameworks/Electron\ Framework.framework ] && echo $app; done
Electron生态开发技术选型
脚手架选型
关于脚手架的选择,其实也很多。
官方提供的有Electron Forge,Electron Fiddle,electron-quick-start,其实如果你的应用不复杂,可以用官方的脚手架生成一个快速上手的模版,然后就可以愉快地开发了。
当然也有一些开源的脚手架,比如electron-vue或vue-cli-plugin-electron-builder之类的,也可以让你快速的生成一个固定的模版,然后往里面填充你的内容。
个人认为,官方的脚手架工具可以用来尝鲜,学习使用,electron-vue这类工具,如果是在一个企业级的项目中使用,前期会给你带来便利,但是后期扩展不会太友好,另外就是他们是基于webpack构建的工具,在日常的开发和使用中会觉得编译得不够快(相对于Vite)。
另外就是如果你想自己完成一个项目脚手架(项目框架),完全可以凭借自己的经验或者参考开源项目的架构自己来完成一个脚手架,一来是为了更加了解Electron的构建原理,二来是可以搭建出适合自己风格项目的脚手架,后期利于扩展和丰富。
所以我们脚手架的选型就是自己来造一个Electron的项目架构,从package.json开始,用Vite+Electron+React构建一个Electron项目。
网络模块选型
Electron发送HTTP请求的方案有很多。
第一种就是渲染进程和主进程分别用相应的请求HTTP请求工具来进行网络请求,比如渲染进程可以使用fetch,主进程用net模块。这种方案的优点就是可以把渲染进程和主进程的请求分开,分工明确,而且调试也方便,渲染进程可以直接看network;缺点就是,如果要对请求进行统一封装的话,比较麻烦。
第二种就是所有的请求统一封装,如果你都使用net模块或者其他的请求工具包对请求进行统一的封装,然后主进程直接使用,渲染进程调用统一的桥接方法。这种方案就是完全可以统一请求封装,但是如果想调试的请求的话,不方便,需要在主进程来日志信息。
第三种就是,直接axios直接一把梭,它既支持node环境,也支持浏览器环境。这种方案非常方便,你就按照之前封装Web应用请求的思路去封装自己的请求模块就行,不过需要注意跨域问题。
对于上面的几种方案,各有各的优缺点,可以根据自己的场景需求来决定使用哪种方案。我们选择了axios来设计网络请求模块。
本地数据库选型
Electron的本地数据存储方式也有很多种,可以直接读写文件,也可以用相关的库,方便数据管理。一些库的对比,详情:https://www.npmtrends.com/electron-store-vs-lokijs-vs-lowdb-vs-nedb-vs-realm。
综合来看lowdb更胜一筹,所以选择lowdb做本地数据库,非常好的一点是它支持同步,不必担心数据没有写入就进行了下一步需要本地数据的业务操作。
日志工具选型
日志工具对Electron的开发也是尤为重要的,可以给你定位到一些表层无法定位的问题,所以一款好的日志工具对开发是非常有帮助的。
比较常见的日志工具就是electron-log和log4js-node,这两款日志工具我都有用过。可以看下npm的排行,这里把express-winston和logging也加上看一下,详情:https://npmtrends.com/electron-log-vs-express-winston-vs-log4js-vs-logging。
这里简单说一下electron-log和log4js-node的比较,两者上手都比较简单,log4js-node暴露的API 非常多,electron-log就稍显逊色了,另外最直观的感受就是,electron-log的日志文件路径不好找,暂时没发现自定义日志路径的方法,log4js-node有相应的方法,而且你可以自定义各种文件类型。
根据使用体验,觉得log4js-node更好,推荐log4js-node。
构建工具选型
三种构建工具electron-builder, electron-forge, electron-packager 对比一下。
从这个排行来看electron-builder的确很强,electron-forge最近又更新大的版本,不过没有尝鲜,我在electron-builder上倒是踩了不少坑,可以分享给大家。所以我在开发的时候选择的构建打包工具是electron-builder,它把整套解决方案都集成了,包括打包、更新、签名、分发,基本的钩子和配置都有相应的暴露。
核心架构实现
架构概览
我们整个框架是基于Eletcorn + Vite构建的,在底层依赖的安全能力和存储模块的基础设施之上设计了一层基础框架,实现构建打包,架构分层的设计,然后给整个桌面应用提供一些应用管理能力和GUI管理相关的能力,最上层就是为了一些业务场景提供的一些应用能力,包括核心的几个应用和主要的策略引擎应用(终端策略和合规策略)。
开发构建
Electron是多进程架构的体系,所以我们在开发构建的时候就是构建多个进程来实现我们的应用。核心思路是通过Vite构建三个进程:渲染进程,任务进程,主进程,然后最后将三个进程融合起来,就形成了一个应用。核心代码如下:
几个注意点:
架构分层
因为需要跨端开发,Mac和Windows有些底层模块的实现还是有不一样的地方,所以我们在开发设计的时候将代码进行了分层设计,这样至上而下的调用在上层看来是一样的,所以我们需要磨平端上底层的差异,现阶段我们底层模块的实现是通过目录来严格区分的,这样在开发一个底层的功能的时候就可以做到各段相互不影响。
打包升级
桌面客户端相当于传统的Web应用在打包和更新这一块还是有非常大的不同的,传统的web应用几乎不用所谓的升级,浏览器刷新页面即可,但是桌面客户端就需要完整的给用户一个可以立即执行的安装应用程序,而且还要可持续迭代和更新,所以在打包升级这一块,我们也是踩了不少坑。
1. 关于打包
打包其实Electron的生态也是非常成熟的,如上面提到的构建技术选型,我们选择的是electron-builder,它提供了一套打包构建升级的流程,暴露了很多API,傻瓜式的配置就基本可以让你实现一个应用的打包了,唯一麻烦的就是签名和认证应用。
在Windows端我们使用pfx格式的证书进行认证,在进行打包的时候会和证书客户端软件交互,完成各个文件的签名,这样用户使用客户端的时候就是签名过的软件了。
在Mac端我们需要使用苹果认证的开发者证书进行签名和认证,配置相应的identity后,构建打包的时候会直接跟你本地的证书进行交互,然后对文件进行签名,当前我们还需要让应用可以不必严格使用 MAP_JIT 标识也能写入和运行内存内容。所以需要加入entitlements和entitlementsInherit。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>
到这一步其实Mac端的软件签名就完成了,但是如果应用想App Store上架的话还需要对应用进行公证。公证主要是使用electron-notarize来进行公证,启用afterSign即可,
afterSign: './script/notarize.js',
下面的Apple ID就是你的开发者账号,appleIdPassword需要生成一个专用的应用密码,不要使用你本来的Apple ID密码。
const { notarize }=require("electron-notarize");
exports.default=async function notarizing(context) {
const { electronPlatformName, appOutDir }=context;
if (electronPlatformName !=="darwin") {
return;
}
const appName=context.packager.appInfo.productFilename;
console.log(\`公证中...\`)
return await notarize({
appBundleId: "mac.hellobike.knight",
appPath: \`${appOutDir}/${appName}.app\`,1
appleId: "XXXXX@outlook.com",
appleIdPassword: "XXXXX",
});
};
notarize会根据你的配置去校验你的应用是否可以公证成功,公证的时候会和苹果的服务器进行通讯,所以需要保持网络不要断开,成功或者失败之后都会发送相应的邮件到你的开发者邮箱里面。
到这里打包的核心工作就做完了,如果你需要其他个性化配置,参考electron-builder官方的文档即可。
2. 关于升级
升级我们在Mac和Windows上的实现各有不同,因为相比于传统的软件,我们哈骑士会一直保活在用户的进程中,所以在更新升级的时候也会打破原本Electron升级的机制。
在Windows上其实还好,可以利用electron-updater本身的生命周期来完成下载,更新,重启应用,因为Windows的保活是用另外的服务来实现的,所以并不会对整个更新周期产生破坏性的影响。
但是Mac端的保活实现是打破了electron-updater本身的生命周期的,探究其源码会发现Electron自己的升级服务其实也是一个保活的应用服务,所以在升级之前需要将其Kill后才能完成哈骑士自己本身的更新逻辑,另外就是文件占用和锁定的问题,为此我们自研了一套更新脚本程序结合electron-updater的下载更新的能力实现了Mac端软件的升级。
核心能力沉淀
基础能力
我们在做哈骑士客户端的时候,也沉淀了一些与业务无耦合的组件和工具类,这些组件和工具在桌面端应用的场景都比较通用。
应用能力
在上面这些基础能力的组合应用下,我们形成了一个强大的策略引擎应用。
该策略引擎应用实现了端上任务调度和分发功能。首先接收后台配置的策略信息,然后生成对应的任务,并分发到各个子任务中心以执行对应的策略。最后,将策略执行情况报告给服务端。
总结
Electron在哈骑士的应用非常成功,虽然在使用过程中遇到了一些问题,但不可否认它是目前最适合我们业务目标和开发资源的框架。使用Electron使需求交付效率得到了很大的提升。
我们也将持续关注性能和稳定性的优化、桌面端全链路日志的完善以及增量更新升级能力等方面的改进。
作者:徐涛焘
来源:微信公众号:哈啰技术
出处:https://mp.weixin.qq.com/s/8v5lyl-yI4AMxQgSwDmkWw
ebView2 是越来越香了。
WebView2 不但是 Win11 自带的系统组件,Win10 也已经自动推送安装。即使是少量没有安装 WebView2 的系统 —— 使用 aardio 中的 web.view 也会自动安装( 不需要写任何代码 )。
我用 WebView2 开发了很多项目,不得不说 WebView2 稳定可靠、性能强悍,接口简洁,是真的让人省心。
这里介绍一个适合用于 WebView2 的极简前端组件 htmx.js ,这个组件最大的特色就是简单,一学就会,也很容易理解。
我们正常浏览一个网页的过程是在浏览器里输入网址,向 HTTP 服务器发送请求。然后服务器返回 HTML 代码,浏览器显示页面。
但是 htmx.js 脑洞大开,让网页上的每一个 HTML 节点都可以向服务器发送请求并获取 HTML,并实时更新页面上指定的节点。而且不需要写任何 JavaScript 代码。
首先我们打开 aardio ,创建 WebView2 工程并选择 htmx.js 模板:
生成的工程如下:
点『运行』可直接测试效果,点『发布』可生成独立 EXE 文件 。
在工程管理器中右键点『网页』弹出菜单,然后点『用外部编辑器打开』,如果安装了 VS Code 会使用 VS Code 打开网页目录。
在 VS Code 中点击并打开 index.html 源码:
打开 index.html ,先看最简单的 htmx.js 示例:
<button hx-get="/api/index.aardio"
hx-swap="innerHTML"
hx-trigger="click"
hx-target="#info-div" >
点这里发送 GET 请求
</button><br>
<div id="info-div"></div>
注意看凡是 "hx-" 前缀的属性都是用于 htmx.js 。
hx-trigger 用于指定在什么事件发生时触发 HTTP 请求,例如:
hx-trigger="click"
表示在 click 单击事件发生时触发请求。
hx-trigger 可使用标准网页事件名,常用事件如下:
事件名后面还可以添加修饰器,例如修饰器 once 表示只允许触发一次 :
hx-trigger="click once"
其他事件修饰器:
下面的 HTML 使用了多个事件修饰器:
<input type="text"
hx-trigger="keyup changed delay:500ms"
hx-post="/api/index.aardio" >
这表示在按键放开( keyup ),文本框的内容发生改变( changed )时触发,并且延时 500 毫秒再发送请求。
hx-get 则指定要请求的是哪个后端页面,例如:
hx-get="/api/index.aardio"
表示事件触发时,请求 "/api/index.aardio" 这个页面。因为 aardio 在启动 SPA 应用时自动指定了后端根目录为 "/web",所以实际请求的是 "/web/api/aardio" 。
而 hx-swap 则指定要将返回的 HTML 写入到哪里,"innerHTML" 指定是更新网页节点内部 HTML,"outerHTML" 指定替换目标网页节点的全部 HTML ,其他还有 "afterbegin" , "beforebegin" , "beforeend" , "afterend" , "none" 。这些看名字就知道是什么作用,就不解释了。
hx-target 属性用 CSS 选择器指定要写入的网页节点,例如:
hx-target="#info-div"
指定服务器返回的 HTML 写入 id 为 "info-div" 的节点。如果省略 hx-target 属性表示写入目标是当前节点自身。
hxmx.js 在更新 HTML 时,如果发现新旧 html 中有 id 相同的元素会进行优化并平滑显示。
看到这里,htmx.js 您已经会用了。
虽然 htmx.js 文档里有更多花式用法,但一般可能用不上。有些事搞太复杂了也不一定是好事。
aardio 提供了嵌入式 HTTP 服务器,可以直接使用 aardio 代码写网页,支持与 PHP 类似的模板语法。
aardio 的模板语法很简单,aardio 代码写在 <? ?> 内部,而 HTML 代码写在 <? ?> 外部就可以了。实际上 <? ?>外部的代码被转换为了 aardio 中 print 函数的参数。
例如服务端有下面的 aardio 代码:
<span>abc</span>
<?
response.write("123")
?>
运行后会自动转换为纯 aardio 代码如下:
print("<span>abc</span>");
response.write("123");
在 HTTP 后端中,print 函数实际上就是指向用于向 HTTP 客户端输出数据的 response.write() 函数。
在 HTTP 后端有两个最常用的对象,request 对象包含了所有 HTTP 请求信息,而 response 对象为 HTTP 响应对象,用于向客户端发送数据。
打开 aardio 自带「工具 > 库函数文档」,点击 fastcgi.client 的文档可以查看 request, response 对象的所有属性与方法。aardio 中的所有 HTTP 服务端实现都统一兼容 fastcgi.client 文档规定的 request, response 用法。
也可以参考 aardio 开始页的 《 aardio 网站开发、FastCGI开发入门教程 》。至于 aardio 模板语法,请参考 《 aardio 语法与使用手册 > aardio 语言 > 模板语法 》
aardio 的模板语法不仅仅可以用于写 HTTP 后端,也不仅仅是可以用于输出 HTML,实际上可以用于生成任何字符串。aardio 中的很多功能都支持这种模板语法,例如运行时编译 C# 代码就支持用 aardio 模板语法生成 C# 代码。另外 aardio 提供 string.loadcode() 函数可以直接解析 aardio 模板并返回字符串。
这里要注意,上面范例工程默认导入的 HTTP 服务器是:
wsock.tcp.simpleHttpServer;
这是一个多线程的 HTTP 服务端,每次被请求执行的 aardio 代码都是在后台线程中运行。aardio 多线程开发要注意的是每个线程都运行在独立的环境,全局变量是相互隔离的,这个限制实际上让 aardio 的多线程开发更简洁,坑更少,具体请参考 aardio 自带「范例程序 > aardio 语言 > 多线程」。
如果改为 wsock.tcp.asynHttpServer 则是单线程异步的 HTTP 服务器。
下面我们仍然使用默认的 simpleHttpServer 。多线程的好处是耗时操作不会卡界面。后端在进行耗时操作时,网页前端通常需要显示一个动画,htmx.js 做这事就很简单。
我们只要简单的修改一下前面讲过的网页代码如下:
<button hx-get="/api/index.aardio"
hx-indicator="#indicator" >
点这里发送 GET 请求
</button><br>
<img id="indicator"
class="htmx-indicator"
src="/images/loading.gif"/>
主要是增加了 hx-indicator 属性,该属性的值用一个 CSS 选择器指定了发送 HTTP 请求时要显示的 HTML 元素,这里指定的是 id 为 "indicator" 的元素。
实际上我们可以自定义这个请求动画的样式,我们打开样式文件 index.css 添加下面的样式:
.htmx-indicator{
display:none;
}
.htmx-request.htmx-indicator{
display:inline;
}
在发送请求时,网页上被设定的指示元素会自动添加 CSS 类 "htmx-request",HTTP 请求结束会移除该类。
然后我们打开对应的 aardio 后端代码 /web/api/index.aardio ,输入以下代码:
<span>
<?
if( request.method=="GET"){
/*
这是多线程后端,
这里等 2 秒,网页会显示加载动画
*/
sleep(2000);
response.write( time() )
}
?></span>
上面的代码的作用是:如果收到 GET 请求,线程就休眠 2 秒以模拟耗时操作。然后输出当前时间。
我们运行一下看看效果:
htmx.js 提交请求的节点如果是一个表单控件,只要指定 name 属性 —— 就会自动以该名字发送请求参数,参数值就是控件的值。
如果提交请求的节点是表单,则 HTTP 请求参数为表单内所有控件的值。
也可以在节点的 hx-vals 属性中用一个 JSON 对象指定请求参数,例如网页这样写:
<button hx-get="/api/index.aardio"
hx-vals='{"myVal": "值"}'>
点这里发送 GET 请求
</button><br>
aardio 后端就可以使用:
request.get["myval"]
取到 HTTP 请求参数 myval 的值。
如果使用 POST 发送请求,例如:
<button hx-post="/api/index.aardio"
hx-vals='{"myVal": "值"}'>
点这里发送 GET 请求
</button><br>
那么 aardio 后端可以使用
request.post["myval"]
取到 HTTP 请求参数 myval 的值。
在 aardio 后端使用:
request.query("myval")
可以取到 GET 或 POST 发送的 myval 参数值。
hx-vals 还可以通过加上 javascript: 或者 js: 前缀后使用 JS 对象返回请求参数,例如:
<button hx-get="/api/index.aardio"
hx-vals='javascript:{myVal: "值"}' >
点这里发送 GET 请求
</button>
有趣的是 web.form 也可以支持 htmx.js 。
web.form 是基于系统自带的 IE 内核控件,注意系统虽然删除了 IE 浏览器,但 IE 控件属于系统组件,Windows 有说明该控件不会被移除。IE 控件的好处是从 XP 到 Win11 所有操作系统都自带。
而且 IE 控件很轻量,与本地程序交互的接口也非常方便。Win10 ,Win11 自带的是 IE11 内核,写写一般的网页还是很好用的。至于 Win7 —— 因为只有极低的份额,一般软件不用考虑。
htmx.js 最后一个支持 IE 11 的版本是 1.6.1 ,这个足够用了。前面说过我们需要的只是局部请求刷新的功能,其他功能我们用不上,所以追新无意义。
首先我们打开 aardio 工程向导,选择「 Web 界面 > Web Form 」然后创建工程即可,新版工程模板默认就是使用 htmx.js 。
其他 HTML 代码写法与前面介绍的 WebView2 基本一样。不过 web.form 本就支持 EXE 内嵌资源文件,所以默认并不会启动 HTTP 服务器,需要多写几句代码。
打开工程的 webPage.aardio 源码:
可以看到源码中是如下启动 HTTP 服务器的:
import web.form;
var wb=web.form(winform);
//多线程后端
import wsock.tcp.simpleHttpServer;
wsock.tcp.simpleHttpServer.documentBase="\web"
var indexUrl=wsock.tcp.simpleHttpServer.startUrl("\index.html")
wb.go(indexUrl);
我并没有把这几句代码封装到 wb.go() 函数中。
有些新手总以为代码越少越好,其实并非如此,有时候多写几句更容易看清楚代码的思路,更容易理解我们正在使用的技术。
下面我们看下 web.form + htmx.js 范例的运行效果:
上面示例程序的主窗口是使用 win.ui.tabs 做的,只有其中一个标签页用到了网页。
其实一般桌面软件的界面并不是一定要全部使用网页实现。有时候我们将界面中适合用网页呈现的部分用网页做,可能会更好。
我们在使用任何技术时,都要考虑一下适不适合。没有一样技术能适合做所有的事,多个选择总是好事。
*请认真填写需求信息,我们会在24小时内与您取得联系。