首页 / Tauri 2 入门教程 / 架构与进程模型

Tauri 2 入门教程

架构与进程模型

本教程共 48 篇 · 第 3 篇 · 更新于 2026-08-09 · 约 6 分钟阅读

TauriTauri 2 入门教程进程模型IPCWRYTAO

本节目标:读完你能画出 Tauri 的进程关系图,并说清核心进程、WebView、IPC 和 WRY/TAO 各自扮演什么角色。

Tauri 的架构和现代浏览器很像:不是一个大进程包揽全部,而是拆成多个相互隔离的进程。理解这一点,是看懂后面所有机制(安全、通信、窗口)的前提。

为什么是多进程,而不是一个进程

早期的图形程序常用单个进程同时算逻辑、画图、听输入。问题很明显:一个耗时计算就会让界面卡死;更糟的是,任何一个部件崩了,整个程序就跟着没了。

后来大家学乖了:把不同组件放进不同进程。好处有两条:

  • 韧性:某个组件崩溃,不会拖垮整个应用;坏掉的进程可以单独重启。
  • 安全:每个进程只给「刚好够用」的权限。这叫最小权限原则——你请园丁来修树篱,给花园钥匙就够,没必要把房门钥匙也交出去。

Tauri 把这套思路用到了桌面应用里。一个应用至少有一个核心进程,下面挂着一到多个 WebView 进程。

核心进程:唯一拥有系统全权的角色

每个 Tauri 应用都有一个核心进程(core process),它是一个 Rust 程序,是整个应用的入口,也是唯一默认拥有完整操作系统权限的部件。

它的主要责任包括:

  • 创建和管理窗口、系统托盘菜单、桌面通知。
  • 通过一套跨平台抽象,把这些系统操作变得简单统一。
  • 路由所有进程间通信(IPC),让你能在一个中心位置拦截、过滤、加工消息。
  • 管理全局状态,比如设置项、数据库连接,方便在多个窗口间同步,也能把敏感数据挡在前端之外。

之所以选 Rust 写核心进程,是因为 Rust 的「所有权」机制在保障内存安全的同时还能保持高性能——不需要垃圾回收器,也就少了一层不确定性。

Note

核心进程崩了,整个应用还是会退出。所以重活、危险活尽量放在 WebView 或异步任务里,别让核心进程卡在同步长计算上。

WebView 进程:负责画界面

核心进程自己不画界面。它启动的是 WebView 进程,由操作系统提供的 WebView 库来跑你的 HTML / CSS / JS。换句话说,你做网页的那套技术栈——框架、打包工具、调试习惯——几乎都能直接搬过来。

和别的方案不同,Tauri 的 WebView 库不打包进最终可执行文件,而是运行时动态链接系统自带的那个。这正是它体积小的原因,但也带来一个后果:不同平台渲染表现可能有细微差别,需要像做普通网页那样做跨平台测试。

各平台的 WebView 对应关系是固定的:

  • Windows → WebView2(Edge 内核)
  • macOS → WKWebView(Safari 内核)
  • Linux → WebKitGTK

IPC 消息传递:前后端怎么对话

进程之间是隔离的,不能直接调用对方的函数。它们靠 IPC(Inter-Process Communication,进程间通信) 来对话。Tauri 用的是一种叫「异步消息传递」的风格:进程之间交换请求和响应这类简单数据,而不是直接共享内存。

这种方式的妙处在于接收方可以自由拒绝。如果核心进程发现某条请求像恶意的,直接丢掉不执行就行,前端拿不到结果也不会出事。

Tauri 的 IPC 有两个原语:

事件(event):一次性、单向的消息,最适合通知「状态变了」「生命周期到了」。事件的特殊之处是前端和核心都能发——前端可以 tell 后端「我准备好了」,后端也能主动 push 给前端「文件读完了」。

命令(command):类似远程函数调用。前端用 invoke 调一个 Rust 函数、传参数、拿返回值,用起来很像浏览器的 fetch。底层走的是类 JSON-RPC 的协议,所以参数和返回值都必须能序列化成 JSON。

Tip

简单记:要「问后端要个结果」用命令(invoke);要「通知对方发生了什么」或「后端主动推数据」用事件(emit / listen)。

WRY 与 TAO:Tauri 脚下的两块地基

Tauri 自己并不直接和系统窗口、浏览器引擎纠缠,它站在两个由 Tauri 团队维护的上游库之上:

  • TAO:负责创建和管理窗口,支持 Windows、macOS、Linux,以及 iOS、Android。它其实是 winit 的一个分支,Tauri 团队按自己的需要扩展了菜单栏、系统托盘等能力。
  • WRY:负责渲染 WebView,是个跨平台 WebView 封装层。它决定「用哪个系统的 WebView、怎么跟它交互」。

你可以把关系想成:你的代码 → Tauri(封装与 API)→ TAO/WRY(和系统对话)→ 操作系统。当你需要超出 Tauri 封装的更深集成时,也能直接使用 TAO 和 WRY。

一个最小的命令示例

下面这段 Rust 定义了一个可被前端调用的命令,并把它登记到应用里:

// src-tauri/src/lib.rs
#[tauri::command]
fn greet(name: &str) -> String {
    format!("你好,{name}!这条消息来自 Rust 后端。")
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("运行 Tauri 应用时出错");
}

前端这样调用它(注意 2.x 里 invoke 来自 @tauri-apps/api/core):

import { invoke } from "@tauri-apps/api/core";

async function sayHello() {
    const msg = await invoke<string>("greet", { name: "小明" });
    console.log(msg); // 你好,小明!这条消息来自 Rust 后端。
}

#[tauri::command] 是个属性宏,它标记「这个函数能从前端的 JS 调」;generate_handler! 则把允许被调用的命令登记进去。两个都要写,前端才调得到。

Warning

invoke 的参数名用 camelCase(如 userName),但 Rust 函数里的参数名是 snake_case(如 user_name)。Tauri 会自动转换,但你在前端传参时写的键必须和 Rust 参数对应上,否则会报错。

小结

Tauri 的架构可以浓缩成一句话:一个拥有系统全权的 Rust 核心进程,管着若干个只负责画界面的 WebView 进程,两者通过异步 IPC(事件 + 命令)对话,底层依赖 TAO 管窗口、WRY 管渲染。

这套「隔离 + 最小权限 + 消息传递」的设计,正是 Tauri 体积和安全感的来源。下一章我们看看 Tauri 从 1.x 走到 2.x 发生了哪些关键变化。