Electron 创建窗口与应用生命周期

使用 BrowserWindow 和应用事件,处理窗口创建、恢复、单实例运行与退出。

🎯 引言

窗口不是随时都可以创建的普通对象。Electron 需要先完成应用初始化,应用退出时也要根据操作系统习惯处理窗口和进程。本篇只讲这些常用生命周期,不加入菜单和业务功能。

学完本篇,你将能够:

  • 在正确的时机创建 BrowserWindow
  • 处理 macOS 的重新激活行为。
  • 让应用只运行一个实例。
  • 保存并清理窗口引用。

🧱 创建窗口的正确时机

窗口应该在 app.whenReady() 完成后创建。主进程可以把创建逻辑放进一个函数,之后在首次启动和 macOS 重新激活时复用它。

electron/main.js
const { app, BrowserWindow } = require('electron/main');
const path = require('node:path');

let mainWindow;

function createMainWindow() {
    mainWindow = new BrowserWindow({
        width: 960,
        height: 640,
    });

    mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));

    mainWindow.on('closed', () => {
        mainWindow = null;
    });
}

app.whenReady().then(() => {
    createMainWindow();
});

保存窗口引用,是为了让主进程之后能够检查窗口是否存在、聚焦窗口或向窗口发送消息。窗口关闭后要清空引用,避免继续使用已经失效的对象。


🪟 处理激活和退出

常见的生命周期事件如下:

事件常见用途
whenReadyElectron 初始化完成后创建窗口
activatemacOS 用户重新点击应用时恢复窗口
window-all-closed所有窗口关闭后决定是否退出应用
electron/main.js
app.whenReady().then(() => {
    createMainWindow();

    app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) {
            createMainWindow();
        }
    });
});

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

Windows 和 Linux 通常在所有窗口关闭后退出,macOS 则常常保留应用进程,等待用户从程序坞再次打开。这个差异由 Electron 应用事件配合操作系统平台判断完成。


🔒 让应用只运行一个实例

桌面应用通常不希望用户重复启动多个相同进程。requestSingleInstanceLock() 可以让后启动的实例退出,并通知已经运行的实例把窗口显示出来。

electron/main.js
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
    app.quit();
} else {
    app.on('second-instance', () => {
        if (!mainWindow) {
            return;
        }

        if (mainWindow.isMinimized()) {
            mainWindow.restore();
        }

        mainWindow.focus();
    });
}

这段代码需要放在创建窗口之前。它只解决“只能有一个应用实例”,不会自动处理深链接、文件关联等后续数据传递问题。


🧾 小节总结

  • BrowserWindow 应在 app.whenReady() 之后创建。
  • 把窗口创建逻辑封装成函数,方便启动和重新激活时复用。
  • 窗口关闭后要清理引用。
  • macOS 常在 activate 时重新创建窗口。
  • requestSingleInstanceLock 可以避免应用重复运行。

❓ 知识问答

Q1:为什么不能在文件加载时直接创建窗口?

A:Electron 的应用环境还没有准备完成,窗口创建可能失败或行为不稳定。应该等待 whenReady

Q2:为什么要判断 mainWindow 是否为空?

A:窗口关闭后引用会被清空,发送消息或聚焦前需要先确认窗口仍然存在。

Q3:单实例锁和多个窗口冲突吗?

A:不冲突。单实例限制的是应用进程数量,一个应用进程仍然可以管理多个窗口。


🧪 小练习

createMainWindow 中的窗口宽度改为 1100,并在 second-instance 事件中补充窗口显示逻辑:

if (mainWindow) {
    // 请在这里编写代码
}

🎉 现在应用已经有了稳定的窗口生命周期。下一篇开始让 Vue 页面通过 preload 和 IPC 调用主进程能力。