理解 Tauri 应用从启动到退出的完整生命周期,有助于你掌握应用在何时做什么事、在哪里挂载全局逻辑,以及如何优雅地处理关闭。下面用最直白的方式梳理这一过程,并附上代码示例。
生命周期的五个阶段
1. 启动
当用户双击可执行文件或通过命令行启动应用时,操作系统将 Rust 编译出的二进制文件加载到内存。此时:
- 应用开始读取
tauri.conf.json中的配置(窗口属性、安全策略、插件列表等)。 - 日志系统初始化,如果你配置了日志文件或输出目标,会开始记录。
- 开发者可以做什么?:在
main()函数中完成全局库的初始化(比如设置环境变量、加载共享库),但通常建议把具体的初始化逻辑放到后面的setup钩子里。
fn main() {
tauri::Builder::default()
.setup(|app| {
// 这里是最佳初始化位置
Ok(())
})
.run(tauri::generate_context!())
.expect("启动失败");
}
2. 初始化(Setup 阶段)
Tauri 调用你注册的 setup 闭包。这是最核心的初始化节点,几乎所有全局一次性操作都应该写在这里:
- 注册自定义 IPC 命令(如
read_file、save_prefs)。 - 初始化状态(如
tauri::State,存放共享数据库连接池、配置对象等)。 - 注册插件(如
tauri-plugin-log、tauri-plugin-store)。 - 执行启动前的检查(如更新迁移、数据库建表)。
真实示例:在 setup 中创建一个数据库连接池,并将其注册为全局状态。
fn main() {
tauri::Builder::default()
.setup(|app| {
let pool = create_db_pool().expect("数据库连接失败");
app.manage(pool); // 注册为全局状态,后续所有命令都可获取
Ok(())
})
.invoke_handler(tauri::generate_handler![my_cmd])
.run(tauri::generate_context!())
.expect("启动失败");
}
3. 窗口创建
初始化完成后,Tauri 根据配置文件中的 windows 数组项逐个创建窗口。默认情况下应用至少会生成一个主窗口,但你可以配置无窗口(如托盘程序)或延迟创建。
- 窗口创建时,Tauri 会加载你指定的前端入口(通常是
dist/index.html),此时 WebView 开始渲染。 - 性能提示:如果窗口较多,可通过
visible: false先隐藏窗口,等前端数据就绪后再显示,避免白屏闪烁。
创建代码示例(在命令或 setup 中动态创建窗口):
use tauri::Manager;
#[tauri::command]
fn open_settings(handle: tauri::AppHandle) {
tauri::WebviewWindowBuilder::new(
&handle,
"settings",
tauri::WebviewUrl::App("settings.html".into()),
)
.title("设置")
.inner_size(600.0, 400.0)
.build()
.unwrap();
}
4. 运行(事件循环)
窗口创建完毕,应用进入主事件循环。此时:
- WebView 与 Rust 后端之间通过 IPC(进程间通信) 交互:前端调用
invoke('command_name', args)触发 Rust 命令,Rust 也可通过事件系统向前端推送数据。 - 系统事件(窗口关闭、拖放、菜单点击、快捷键等)被分发处理。
- 你可以在 Rust 中监听窗口或应用事件,例如监听窗口关闭请求来决定是否阻止。
阻塞关闭以询问用户保存数据的例子:
// 在 setup 中监听主窗口的关闭事件
let handle_clone = app.handle().clone();
let window = app.get_webview_window("main").unwrap();
window.on_window_event(move |event| {
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
// 阻止直接关闭,改为隐藏或询问
api.prevent_close();
let win = handle_clone.get_webview_window("main").unwrap();
win.hide().unwrap();
// 可向前端发送事件询问是否保存
}
});
5. 退出
当所有窗口关闭或程序主动调用 app.exit() 时,触发退出流程:
- Tauri 会执行清理:销毁所有窗口,调用插件和注册资源的
Drop实现,释放内存。 - 你可以注册
on_exit钩子,在正式退出前做最后的收尾(例如写入日志、上传崩溃数据、删除临时文件)。但这个钩子不能保证一定会执行(例如被强制杀死),重要数据应在使用时就保存。
tauri::Builder::default()
.on_exit(|app, _event| {
// 退出前执行
println!("应用正在退出,bye!");
})
// ...
退出触发的常见方式:
- 用户点击窗口关闭按钮(且无阻断)。
- 调用
app_handle.exit(0)。 - 系统关机或 Ctrl+C(控制台环境)。
一张图总结
启动 → [main()] → .setup() → 创建窗口 → 进入事件循环 → 处理 IPC/事件 → 退出清理
掌握这个生命周期后,你就能把初始化、业务逻辑、清理操作安放到正确的位置,避免在全局作用域里做耗时操作或忘记释放资源。开发时可利用 setup 集中管理依赖项,在命令中安全操作状态,在退出钩子中做好收尾——这样的应用会既稳健又轻量。