本文为来自 教育-智能学习-前端团队成员的文章,已授权 ELab 发布。
智能学习前端团队自创立以来,团队专注于打破大众对教育的刻板印象,突破固有的教学思维,攻破各类教学屏障。旨在为每一位学生制定最合适的学习方案,予以因材施教,使优质教育随 "触" 可达。
在处于移动互联网的当下,虽然桌面应用的重要性已经不能同往日而语,但在我们平常的日常工作和生活中,还是扮演着非常重要的角色和地位。在我们的日常工作中,离不开Lark、VSCode等桌面应用。
相比较于移动端而言,桌面端应用的生态多种多样,因此也诞生了各种各样的桌面应用开发技术栈。本次分享将会对相关常用的一些桌面应用开发框架进行介绍和分析,同时对当下比较流行(Github 50k star)的跨平台桌面应用开发框架Tauri进行介绍。
原生技术栈是指通过操作系统相关API或者操作系统厂家(如Apple/Microsoft)提供的SDK/工具来开发桌面应用的方式。使用原生技术开发的应用,通常能够在性能、体积以及系统的交互等方面做到非常不错的效果。
作为目前使用率最高的操作系统,Windows平台的GUI程序开发经历了漫长的迭代和演化的过程:Win32 API作为Windows GUI开发的鼻祖,通过C语言调用Windows底层的绘图函数来进行开发;在Win32 API之后出现了MFC(Microsoft Fundation Class),MFC通过C++语法将原有的Win32 API封装成了控件类(对话框控件、按钮控件等);在MFC之后,微软推出了Windows Form(2002年),Windows Form依赖于.NET的运行时,提供了组件化的开发能力;在此之后,微软推出了WPF(Windows Presentation Fundation,2006年),WPF提供了基于XML的语言XAML来描述UI;在Windows8的时代,微软又推出了UWP(Universal Windows Platform,2015年),UWP支持在各种平台上运行(PC/Windows Phone/Xbox),API也支持多种语言(C++/VB/C#/JS)。
从Windows平台应用的开发技术迭代来看,也可以大致看出GUI程序的技术发展史:
现有的macOS原生应用主要基于Cocoa框架开发,Cocoa是从1980年代由NeXT(macOS的前身)开发的编程环境NeXTSTEP和OPENSTEP演变而来,是面向对象的API。
在2020年的WWDC上,苹果推出了新一代的UI框架SwiftUI,和Flutter/React等现代GUI框架类似,支持声明式的方式使用Swift语言作为DSL来编写UI,同时也支持跨平台的特性,可以在macOS/IOS/tvOS等多平台运行。
Linux其源码只包含了操作系统内核的部分,桌面并不属于Linux源码的一部分,因此严格意义上来说,从「使用系统API和操作系统厂商提供的SDK开发的应用为原生应用」的定义上来说,并无所谓「原生技术栈」的概念。我们日常使用的发行版提供了桌面环境如KDE、Gnome等,Linux发行版的这些桌面环境也提供了相关的一些库或者API来帮助绘制GUI程序,如gtk+等,可以认为是「原生技术栈」。
Atwood's Law: Any application that can be written in JAVAScript, will eventually be written in JavaScript.
一个你或许不知道的冷知识,macOS的系统设置页面是Webview+React写的[1]
提到跨平台,就不得不提Web生态了,Web相关的技术在跨平台中永远是最受青睐的选择,无论是开发的便捷程度,还是庞大的JS开发者生态等等因素,都使得Web技术无论是在移动端还是桌面端的跨平台应用开发上都稳坐使用率最高的技术栈。
Electron(原名为Atom Shell)是GitHub开发的一个开源框架,最初用来开发Atom编辑器。它通过使用Node.js(作为后端)和Chromium的渲染引擎(作为前端)完成跨平台的桌面GUI应用程序的开发。
CEF(Chromium%20Embedded%20Framework) 由于需要将Chromium和NodeJS的运行时打包进去,所以Electron构建应用的体积都会非常大,但是CEF的存在解决了Electron的这个问题(实际上,CEF出现的时间比Electron早多了)。由于Chromium里面有许多第三方组件(如ffmpeg等),在开发应用的过程中,我们通常不会使用到Chromium的全部能力,因此CEF提供了一个轻量级的嵌入式Chromium,同时还可以根据自己的需求进行裁剪。
CEF提供了将Chromium嵌入到应用中,展示Webview的能力,同时也提供了C++的一些API,在需要做一些浏览器无法实现的原生API依赖的功能时(如系统文件读写等),则需要使用C++(或其他语言,但是CEF的原生接口是C++的)来编写相关的能力,并提供JSBridge给前端代码进行调用。
自渲染技术栈 要实现跨平台的GUI应用,比较流行的的方式是实现自渲染的管线。上层通过提供类Canvas的绘制、渲染和排版能力,下层使用OpenGL/Vulkan/Metal等图形API进行绘制。在Web的跨平台桌面应用开发技术栈发展之前,许多应用开发框架都采用了类似的思路去实现跨平台的应用开发,如QT(C++语言)、Flutter(Dart语言,基于Skia渲染)和Swing(Java语言)等。相比于Electron和CEF的方案,由于不需要打包运行时环境(Swing除外,需要打包JRE)和减少了Bridge转换,所以体积和运行效率通常会优于Web技术栈。
Qt(C++) Qt(/ˈkjuːt/)是一个跨平台的C++应用程序开发框架,广泛用于开发GUI程序,在工业、嵌入式等领域的桌面程序中有着非常深入的使用。
Flutter(Dart) Flutter是一个由google开发的跨平台应用开发框架,最初只用于移动端为Android、iOS开发应用。2015年4月,Flutter正式发布,其目标是希望可以在跨平台的特性上,实现120FPS的渲染性能。2018年,Flutter%201.0发布,是该框架的第一个稳定版本。2022年5月,Google%20在%20Google%20I/O%202022%20发布了%20Flutter%203.0版本,宣布对%20Windows、macOS、Linux%20桌面操作系统提供支持。
Swing是一个用于开发Java GUI应用的框架。它采用纯 Java 实现,不再依赖于本地平台的图形界面,所以可以在所有平台上保持相同的运行效果,对跨平台支持比较出色。
总的来说,虽然不同大类技术栈的应用具体实现原理有所不同,但是相关开发的技术栈的大致特点可以归纳如下:
从上面的介绍可以看出,不同的技术栈的实现原理和特点各有区别,但是很难做到开发便捷程度、UI复杂效果、打包体积和性能等多个方面的兼顾,只能是根据应用的类型和具体的业务场景去决定到底使用哪种框架。
所以有没有一种开发方式,可以在性能、体积、开发等多个角度上,取得一个比较好的平衡呢?这就来到了我们今天需要介绍的桌面应用开发框架Tauri。
Build an optimized, secure, and frontend-independent application for multi-platform deployment.
从上面Tauri官网的宣传语可以看出Tauri主打的几个卖点[4]:
那么Tauri是如何通过在框架层面的设计来保证这样的一些特性的呢?我们一起接着往下看⬇️
Tauri框架是由Rust语言实现的,同时Tauri应用的后端也是由Rust来编写的。Rust是由Mozilla主导开发的通用、编译型的系统编程语言。设计准则为“安全、并发、实用”,支持函数式、并发式、过程式以及面向对象的编程风格。[5]相比较其他语言,Rust有如下的一些特性:
由于Web技术的表现力强、开发成本低的特点,与Electron、CEF等框架类似,Tauri应用的前端实现也是使用Web技术栈编写的。那么Tauri是如何解决Electron/CEF等框架遇到的Chromium内核体积过大的问题呢?
大家可能会想,如果每一个应用都需要把浏览器内核打包进去才能实现Web页面的渲染,要是所有的应用都共享同一个内核就好了,这样我们在分发应用的时候,不需要打包浏览器内核,只需要打包Web页面的资源不就好了吗?所以Tauri就采用了这样的一个方案,WRY是Tauri封装的Webview框架,它在不同操作系统的平台上,封装了系统Webview的实现:在macOS上使用WebKit.WKWebview[2],在Windows上使用Webview2[3],在Linux上使用WebKitGTK[4]。这样在运行Tauri应用时,会直接使用系统Webview来渲染应用前端的展示。
对于不会使用Rust的同学来说,学习Rust还是存在着不少的学习成本,但是别担心,在需求简单的情况下,你完全可以不写Rust代码。Tauri框架提供了如下的一些API,可以方便地在JS中对原生能力进行调用:
需要注意的是,上述的一些API,为了保证安全性,对于权限都有着严格的限制,都是默认关闭的状态,需要修改配置以手动启用相关的功能。
和Electron类似,Tauri也采用的是多进程的架构(Electron中有主进程和渲染进程),多进程的好处是能够更好更有效地利用现代多核CPU的能力,同时一个组件的崩溃也不会影响到其他组件的运行,因为组件被隔离在了不同的进程中。如果应用中的某个进程崩溃了,我们只要重启该进程即可。还可以通过只给每个进程分配足够完成工作的最低限度的权限,来限制潜在漏洞的破坏范围。这种模式被称为最小权限原则。
在Tauri中,进程分为两类:主进程和Webview进程。每个 Tauri 应用程序都有一个主进程,它作为应用程序的入口点,是唯一可以完整访问操作系统的组件。主进程的主要职责是使用访问权限来创建和管理应用程序窗口、系统托盘菜单或通知。Tauri 实现了必要的跨平台抽象来简化该操作。它还通过核心进程路由所有的IPC,通过类似于事件总线的机制,可以方便地拦截、过滤和操作 IPC 消息。主进程还应该负责管理全局状态,例如数据库连接。这使你能够轻松地在窗口间同步状态,并保护你的业务敏感数据。主进程自身并不渲染实际的用户界面,它会直接利用操作系统提供的 WebView 库来实现页面渲染,不同的窗口之间会拥有不同的WebView进程,WebView进程用来负责渲染对应的UI。
Brownfield 软件开发是指在现有或遗留软件系统存在的情况下开发和部署新的软件系统。[6]
使用 Tauri 的最简单和直接的模式,因为 Brownfield 模式会尽最大可能尝试与现有的前端项目兼容。在这种模式下,无需现有的浏览器前端项目进行任何操作即可迁移。
隔离模式是一种在到达 Tauri 主进程前,拦截并修改由Webview进程发送的 Tauri API 信息的IPC模式,其完全使用 JS 编写。由隔离模式保障的JS代码即称为隔离应用。隔离模式的目的是为开发者提供一种保护机制,防止其应用程序被预料之外或恶意的Webview进程调用 Tauri 主进程。隔离模式的需求来自于前端中不可信任的内容所带来的威胁,常见于需要许多依赖的应用。隔离模式在设计之初时设想的最大威胁为开发威胁,因为前端构建工具不仅仅由许许多多嵌套很深的依赖组成,而且还有很多嵌套很深的依赖被打包到最终的网页构建产物中。
可以有效防止恶意页面被加载
因为Tauri应用的前端是用了Web相关的技术栈,因此在运行时总有办法来使用开发者模式/调试工具等来进行检查元素等。在某些情况下,如密码输入等场景,我们希望能够保证前端的UI展示是无法被修改的,这时可以使用Tauri-egui这个库,这个库对egui进行了封装,可以使用Rust来编写前端的UI。
[1] 在macOS中打开系统Webview的检查元素开关,
https://blog.jim-nielsen.com/2022/inspecting-web-views-in-macos/
[2] 微软开始在Teams应用中放弃Electron,
https://blog.devgenius.io/microsoft-is-finally-ditching-electron-9e081757f9db
[3] VSCode优化括号颜色匹配,
https://code.visualstudio.com/blogs/2021/09/29/bracket-pair-colorization
[4] Tauri官网, https://tauri.app
[5] Rust Wikipedia, https://zh.wikipedia.org/zh-cn/Rust
[6] Brownfield, https://en.wikipedia.org/wiki/Brownfield_(software_development)
以上便是本次分享的全部内容,希望对你有所帮助^_^