2024年Tauri:下一代桌面应用开发框架?(1),斗鱼C C++开发二面被刷

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

自渲染技术栈

要实现跨平台的GUI应用,比较流行的的方式是实现自渲染的管线。上层通过提供类Canvas的绘制、渲染和排版能力,下层使用OpenGL/Vulkan/Metal等图形API进行绘制。在Web的跨平台桌面应用开发技术栈发展之前,许多应用开发框架都采用了类似的思路去实现跨平台的应用开发,如QT(C++语言)、Flutter(Dart语言,基于Skia渲染)和Swing(Java语言)等。相比于Electron和CEF的方案,由于不需要打包运行时环境(Swing除外,需要打包JRE)和减少了Bridge转换,所以体积和运行效率通常会优于Web技术栈。

  • 优点

    • 自绘性能通常会优于Web跨平台技术(具体还是取决于框架实现)
  • 缺点

    • 开发成本略高于Web技术栈
    • 实现复杂效果的能力不如原生和Web技术栈,通常情况下需要写更多复杂的代码(取决于具体框架的设计,这一点Flutter做得比较好)
Qt(C++)

Qt(/ˈkjuːt/)是一个跨平台的C++应用程序开发框架,广泛用于开发GUI程序,在工业、嵌入式等领域的桌面程序中有着非常深入的使用。

  • 代表应用:WPS Office、剪映桌面版、AutoDesk

  • 优点

    • 性能好,与Native开发的应用性能相差无几
    • 支持的操作系统丰富,跨平台性能好
  • 缺点

    • C++开发成本高
    • GPL协议,商业版本需要给钱,不符合咱们去肥增瘦的理念
Flutter(Dart)

Flutter是一个由Google开发的跨平台应用开发框架,最初只用于移动端为Android、iOS开发应用。2015年4月,Flutter正式发布,其目标是希望可以在跨平台的特性上,实现120FPS的渲染性能。2018年,Flutter 1.0发布,是该框架的第一个稳定版本。2022年5月,Google 在 Google I/O 2022 发布了 Flutter 3.0版本,宣布对 Windows、macOS、Linux 桌面操作系统提供支持。

3568dbdac7c4f5b377fb64a4dc7ea0f5.png

  • 代表应用:?(Flutter在2022年5月份发布3.0版本,此时桌面应用才进入正式版支持,目前还并不成熟,所以在线上使用的较少,暂时没听过有啥桌面应用是用Flutter写的)

  • 优点

    • 性能好(相比较Web技术栈)
    • Dart语言容易学习和上手、开发成本低
  • 缺点

    • 桌面端才刚刚发布稳定版支持,生态和稳定性都有待考量
Swing(Java)

Swing是一个用于开发Java GUI应用的框架。它采用纯 Java 实现,不再依赖于本地平台的图形界面,所以可以在所有平台上保持相同的运行效果,对跨平台支持比较出色。

  • 代表应用:Jetbrains IDE

  • 优点

    • 跨平台性能好:write once run anywhere (write once debug everywhere)
  • 缺点

    • 需要打包JRE,体积大

总的来说,虽然不同大类技术栈的应用具体实现原理有所不同,但是相关开发的技术栈的大致特点可以归纳如下:

  • 系统API调用和交互:原生应用 > 自渲染应用 > Web应用
  • 开发便捷程度:Web应用 >> 自渲染应用 > 原生应用
  • 打包体积:Web应用 > 自渲染应用 > 原生应用
  • 性能:原生应用 > 自渲染应用 > Web应用

Tauri介绍

从上面的介绍可以看出,不同的技术栈的实现原理和特点各有区别,但是很难做到开发便捷程度、UI复杂效果、打包体积和性能等多个方面的兼顾,只能是根据应用的类型和具体的业务场景去决定到底使用哪种框架。

所以有没有一种开发方式,可以在性能、体积、开发等多个角度上,取得一个比较好的平衡呢?这就来到了我们今天需要介绍的桌面应用开发框架Tauri。

Build an optimized, secure, and frontend-independent application for multi-platform deployment.

从上面Tauri官网的宣传语可以看出Tauri主打的几个卖点[4]:

  • optimized:性能高、体积小
  • secure:安全性强
  • frontend-independent:前端独立
  • multiplatform:跨平台

那么Tauri是如何通过在框架层面的设计来保证这样的一些特性的呢?我们一起接着往下看⬇️

Rust

Tauri框架是由Rust语言实现的,同时Tauri应用的后端也是由Rust来编写的。Rust是由Mozilla主导开发的通用、编译型的系统编程语言。设计准则为“安全、并发、实用”,支持函数式、并发式、过程式以及面向对象的编程风格。[5]相比较其他语言,Rust有如下的一些特性:

  • 性能高(optimized):Rust的性能和C/C++的性能不相上下,由于Rust的「所有权」机制,Rust不需要GC,同时也能避免如C/C++之类需要手动管理内存的语言忘记释放内存导致的内存泄露的问题;
  • 安全性强(secure):Rust设计了一个所有权系统,其中所有值都有一个唯一的所有者,并且值的作用域与所有者的作用域相同。值可以通过不可变引用(&T)、可变引用(&mut T)或者通过值本身(T)传递。任何时候,一个变量都可以有多个不可变引用或一个可变引用,这实际上是一个显式的读写锁。Rust编译器在编译时强制执行这些规则,并检查所有引用是否有效。能够有效避免C/C++等语言中的悬垂指针等问题;
  • FFI编译友好(multiplatform):FFI是可以用一种编程语言写的程序能调用另一种编程语言写的代码的机制,使用Rust可以方便地提供接口给其他语言调用;

WRY[1]:Webview Render Library

由于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来渲染应用前端的展示。

API接口

对于不会使用Rust的同学来说,学习Rust还是存在着不少的学习成本,但是别担心,在需求简单的情况下,你完全可以不写Rust代码。Tauri框架提供了如下的一些API,可以方便地在JS中对原生能力进行调用:

  • cli:解析应用启动时的命令行参数
  • clipboard:对系统剪贴板的读写
  • dialog:展示系统文件选择、文件保存弹窗
  • event:给后端发出一些事件,后端监听并处理
  • fs:文件系统的操作,提供文件读写等能力
  • globalShortcut:注册全局快捷键
  • http:使用Rust的Http客户端进行网络请求
  • notification:系统通知
  • os:获取操作系统的一些信息
  • path:文件和文件夹路径处理的一些工具
  • process:对当前的进程进行一些操作
  • shell:对系统shell的一些操作

需要注意的是,上述的一些API,为了保证安全性,对于权限都有着严格的限制,都是默认关闭的状态,需要修改配置以手动启用相关的功能。

进程模型

和Electron类似,Tauri也采用的是多进程的架构(Electron中有主进程和渲染进程),多进程的好处是能够更好更有效地利用现代多核CPU的能力,同时一个组件的崩溃也不会影响到其他组件的运行,因为组件被隔离在了不同的进程中。如果应用中的某个进程崩溃了,我们只要重启该进程即可。还可以通过只给每个进程分配足够完成工作的最低限度的权限,来限制潜在漏洞的破坏范围。这种模式被称为最小权限原则。

在Tauri中,进程分为两类:主进程和Webview进程。每个 Tauri 应用程序都有一个主进程,它作为应用程序的入口点,是唯一可以完整访问操作系统的组件。主进程的主要职责是使用访问权限来创建和管理应用程序窗口、系统托盘菜单或通知。Tauri 实现了必要的跨平台抽象来简化该操作。它还通过核心进程路由所有的IPC,通过类似于事件总线的机制,可以方便地拦截、过滤和操作 IPC 消息。主进程还应该负责管理全局状态,例如数据库连接。这使你能够轻松地在窗口间同步状态,并保护你的业务敏感数据。主进程自身并不渲染实际的用户界面,它会直接利用操作系统提供的 WebView 库来实现页面渲染,不同的窗口之间会拥有不同的WebView进程,WebView进程用来负责渲染对应的UI。

c3717f5d4f97f88c31762deb2d25f623.png

IPC模式

Brownfield 模式(默认)

Brownfield 软件开发是指在现有或遗留软件系统存在的情况下开发和部署新的软件系统。[6]

使用 Tauri 的最简单和直接的模式,因为 Brownfield 模式会尽最大可能尝试与现有的前端项目兼容。在这种模式下,无需现有的浏览器前端项目进行任何操作即可迁移。

隔离模式

隔离模式是一种在到达 Tauri 主进程前,拦截并修改由Webview进程发送的 Tauri API 信息的IPC模式,其完全使用 JS 编写。由隔离模式保障的JS代码即称为隔离应用。隔离模式的目的是为开发者提供一种保护机制,防止其应用程序被预料之外或恶意的Webview进程调用 Tauri 主进程。隔离模式的需求来自于前端中不可信任的内容所带来的威胁,常见于需要许多依赖的应用。隔离模式在设计之初时设想的最大威胁为开发威胁,因为前端构建工具不仅仅由许许多多嵌套很深的依赖组成,而且还有很多嵌套很深的依赖被打包到最终的网页构建产物中。

  • 原理:隔离模式就是在Webview进程和 Tauri 主进程之间注入一个安全的应用程序,用以拦截和修改传入的 IPC 信息。它使用 <iframe> 的沙盒特性,与Webview进程一起安全地执行 JS 代码。Tauri 在加载页面时会强制执行隔离模式,使所有对 Tauri 主进程的 IPC 调用必须先通过沙盒隔离应用程序。当消息准备被传递给 Tauri 主进程时,其就会被使用浏览器的 SubtleCrypto API[5] 实现加密,并传递回主前端程序,之后,它将会被直接传递给 Tauri 主进程来解密和读取数据。
  • 步骤:

50e1fd787c8bcf3ea092c206882b9016.png

image.png

  • 缺点

    • 由于信息经过加密,所以隔离模式相比 Brownfield 模式而言存在额外开销。除去性能敏感的应用 (使用很少依赖来提升性能的应用) 之外,使用 AES-GCM 算法来加解密 IPC 信息会对大部分应用造成相对较小的性能影响。
    • Windows平台上,Webview限制了因为沙盒环境下加载 <iframe> 标签内的外部文件,Tauri在构建时实现一些步骤将脚本注入,但是ES Modules可能无法正常加载。

安全性

  • 动态AOT:Tauri 应用启动时将多次进行编译。通过使用Tauri提供的动态预编译器,可以生成每个会话都不同的代码引用,但技术上一致的代码单元;
  • 函数 ASLR:函数地址空间布局随机化 (Address Space Layout Randomization) 将在运行时随机调整函数名称,且可以实现 OTP 哈希功能,这样将永远不会出现相同的会话。在Tauri应用启动时,或可选在每次执行后,随机生成函数名称。每个函数指针均使用 UID 以防止静态攻击;
  • 自杀式函数注入:一种高级的ASLR技术,Rust在运行时载入进入Webview的闭包Promise和随机的句柄,API接口在Promise处理中就被锁定,在执行完毕后被立即设置成null;

可以有效防止恶意页面被加载

  • 使用Event Bridge:Tauri内部的信息通信使用Event Bridge,Event Bridge用来保证只能传递信息和指令给一个指定的中间代理,而不是直接传递不安全的函数调用;
  • API Allow List:Tuari提供的API都是关闭状态,若没有启用相关API,应用构建时不会包括相关功能函数的代码。这可以减少二进制文件大小及攻击面。同时API还有严格的权限选项,如文件读写相关API可以设置只读/只写/指定目录或文件等功能;
  • CSP:Tauri会对本地的HTML页面强制使用内容安全策略,本地脚本经过哈希处理,同时样式、外部脚本经由加密随机字符串引用,防止禁止内容被加载;
  • 反编译难:Tauri在构建时,会将相关前端代码直接构建在二进制可执行文件中,这意味着和Electron的ASAR文件不同,具体的代码无法被轻松地反编译;
  • 一次性Token和哈希:使用OTP加盐哈希处理重要的信息,可以在前端和Rust后端之间加密信息;

Tauri-egui[6]

因为Tauri应用的前端是用了Web相关的技术栈,因此在运行时总有办法来使用开发者模式/调试工具等来进行检查元素等。在某些情况下,如密码输入等场景,我们希望能够保证前端的UI展示是无法被修改的,这时可以使用Tauri-egui这个库,这个库对egui进行了封装,可以使用Rust来编写前端的UI。

0ae20264b1879c1b84ed9235b55f2aeb.png

优点

c95dd5c47d3653bc901bd74fa93a1b58.png

Benchmark

  • 内存使用

3ae6ac0f82aae8a291299420efd69615.png

  • 构建产物体积

9625056a733ab5726e8da3ace907ae96.png

缺点

  • 兼容性:由于Tauri使用的是系统Webview,因此在构建时前端的代码需要做polyfill;且Windows平台上,由于Webview2只在Windows10/11上有默认推送安装,要想在Windows7/8等较低版本的平台上运行的话,还需要另外安装Webview2的运行时。

参考资料

[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)

❤️ 谢谢支持

以上便是本次分享的全部内容,希望对你有所帮助_

喜欢的话别忘了 分享、点赞、收藏 三连哦~。

如果想学习更多H5游戏webpacknodegulpcss3javascriptnodeJScanvas数据可视化等前端知识和实战,欢迎在《趣谈前端》加入我们的技术群一起学习讨论,共同探索前端的边界。

b3493ea02e895c860da1169c97ea44b6.gif

从零搭建全栈可视化大屏制作平台V6.Dooring

从零设计可视化大屏搭建引擎

Dooring可视化搭建平台数据源设计剖析

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

外链图片转存中…(img-EC37Ejw3-1715586511431)]
[外链图片转存中…(img-t7g0EHRG-1715586511432)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Tauri 中,`App::handle`、`tauri::Builder::default()` 和 `tauri::WindowBuilder::new` 是三个重要的 API,它们分别用于处理应用程序的事件、构建 Tauri 应用程序和创建窗口。 ### App::handle `App::handle` 方法用于处理 Tauri 应用程序的事件。Tauri 是一个基于 Rust 和 Web 技术栈的框架,运行在基于 Chromium 内核的 WebView 中。`App::handle` 方法会监听应用程序的事件,例如启动、关闭、最小化、最大化等,并且可以根据这些事件来执行相应的操作。 以下是一个简单的示例,使用 `App::handle` 方法监听应用程序的启动事件,并在启动时打印一条消息: ```rust fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![my_custom_handler]) .run(tauri::generate_context!()) .expect("failed to run app"); } #[tauri::command] fn my_custom_handler() { println!("My Tauri app has started!"); } ``` ### tauri::Builder::default() `tauri::Builder::default()` 方法用于构建 Tauri 应用程序。在构建 Tauri 应用程序时,可以定义应用程序的窗口、菜单、打印机、文件选择器等属性。使用 `tauri::Builder::default()` 方法可以获取一个默认的构建器实例,你可以在此基础上进行进一步的配置。 以下是一个简单的示例,使用 `tauri::Builder::default()` 方法构建一个窗口: ```rust fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![my_custom_handler]) .run(tauri::generate_context!()) .expect("failed to run app"); } #[tauri::command] fn my_custom_handler() { let window = tauri::WindowBuilder::new() .title("My Tauri App") .build() .unwrap(); window.show().unwrap(); } ``` ### tauri::WindowBuilder::new `tauri::WindowBuilder::new` 方法用于创建窗口。在创建窗口时,可以定义窗口的标题、大小、位置、图标等属性。使用 `tauri::WindowBuilder::new` 方法可以获取一个默认的窗口构建器实例,你可以在此基础上进行进一步的配置。 以下是一个简单的示例,使用 `tauri::WindowBuilder::new` 方法创建一个窗口: ```rust fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![my_custom_handler]) .run(tauri::generate_context!()) .expect("failed to run app"); } #[tauri::command] fn my_custom_handler() { let window = tauri::WindowBuilder::new() .title("My Tauri App") .width(800) .height(600) .build() .unwrap(); window.show().unwrap(); } ``` 以上三个 API 是 Tauri 应用程序开发中的重要组成部分,通过它们可以构建 Tauri 应用程序,并且对应用程序的事件进行处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值