人人都会AI编程

3.3 应用完整生命周期:启动 → 初始化 → 窗口创建 → 运行 → 退出

更新时间:2026-07-11

理解 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_filesave_prefs)。
  • 初始化状态(如 tauri::State,存放共享数据库连接池、配置对象等)。
  • 注册插件(如 tauri-plugin-logtauri-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 集中管理依赖项,在命令中安全操作状态,在退出钩子中做好收尾——这样的应用会既稳健又轻量。