app 模块是 Electron 应用的入口,也是整个应用生命周期的控制中心。它不需要你手动引入——Electron 在启动时会自动创建 app 对象,你只需在主进程中直接使用它。掌握了 app 模块,你就能控制应用何时启动、如何退出、在哪里存储数据,以及如何在不同生命周期阶段执行自定义逻辑。
7.1.1 应用生命周期:从启动到退出
Electron 应用的一生可以划分为几个明确的阶段。理解这些阶段,对于正确管理窗口、保存状态和释放资源至关重要。
(1)启动阶段
应用启动时,Electron 会先加载你的主进程脚本(通常是 main.js)。此时,你需要注册生命周期事件的监听器,但还不能立即创建窗口——app 模块尚未完全就绪。
const { app } = require('electron');
// ❌ 错误:此时 app 尚未 ready,创建窗口会失败
// const win = new BrowserWindow();
// ✅ 正确:只做事件注册,不操作 UI
app.on('ready', () => {
// 在这里创建窗口
});
(2)ready 事件
当 Electron 完成初始化后,会触发 ready 事件。只有在这个事件之后,你才能安全地创建 BrowserWindow 实例或调用大部分 Electron API。推荐使用 app.whenReady() 方法,它返回一个 Promise,并且更简洁:
app.whenReady().then(() => {
createWindow(); // 创建主窗口
// 在 macOS 上,即使所有窗口都关闭也保持应用运行
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.whenReady() 优于直接监听 'ready' 事件,因为它本身返回 Promise,更方便搭配 async/await。
(3)运行阶段
应用启动后,会根据用户操作或系统事件运行。这个阶段主要以窗口的创建和销毁、主进程与渲染进程的通信为主。macOS 还有一个特殊事件 'activate':当用户点击程序坞图标且没有窗口打开时触发,通常用于重新创建窗口。
(4)关闭阶段
关闭应用并非简单粗暴地结束进程,而是需要经历一组连续的事件。理解这几个事件的顺序,可以让你在用户关闭窗口时执行数据保存、清理临时文件等操作。
window-all-closed
当所有窗口都关闭时触发。如果你没有监听这个事件,Electron 的默认行为是:
- 在 Windows 和 Linux 上,直接退出应用。
- 在 macOS 上,应用保持运行(因为 macOS 应用通常在窗口关闭后继续存活)。
你可以自定义行为,例如在非 macOS 平台上退出应用:
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
before-quit
在应用开始执行退出流程之前触发。这是保存应用状态(如窗口位置、最近打开的文件列表)的最后时机。注意:这个事件在 app.quit() 调用后才触发,而 window-all-closed 在其之前。
app.on('before-quit', () => {
// 保存配置到本地文件
fs.writeFileSync('config.json', JSON.stringify(state));
});
will-quit
在所有窗口都已关闭、除了主进程外再无其他进程运行时触发。此时你可以执行一些同步清理操作,比如关闭数据库连接。注意,如果应用是通过 app.quit() 正常退出,这个事件会触发;如果是强制关闭(如任务管理器结束进程),则不会。
app.on('will-quit', () => {
database.close();
});
quit
应用已经退出,这个事件在 will-quit 之后,此时除了少量清理,不建议再执行耗时操作。在 macOS 中,通过程序坞右键退出应用会直接触发 quit,但 will-quit 可能不会触发,因此关键清理逻辑最好放在 before-quit 或 will-quit 中。
事件的典型触发顺序(正常退出):
- 用户关闭最后一个窗口
window-all-closed- (如果你调用了
app.quit())before-quit - 窗口关闭,其他进程退出
will-quit- 应用退出
在开发中,最常用的模式是在 before-quit 中保存状态,在 will-quit 中做最后的资源释放。
7.1.2 常用方法与属性
除了生命周期事件,app 模块还提供了许多实用工具,直接关系到应用的数据存储、路径管理和版本控制。
(1)app.getPath(name)
获取系统预设目录的绝对路径。name 可取以下常用值:
| 名称 | 说明 |
|------|------|
| home | 用户的主目录 |
| appData | 当前用户的应用数据目录(通常是 %APPDATA% 或 ~/Library/Application Support) |
| userData | 用于存储应用配置文件的目录(推荐存放用户数据) |
| desktop | 用户桌面目录 |
| documents | 用户“文档”目录 |
| downloads | 下载目录 |
| temp | 临时目录 |
| exe | 应用程序可执行文件所在目录 |
| logs | 日志目录 |
最佳实践:将用户配置、数据库、缓存等数据放在 app.getPath('userData') 下,避免污染应用安装目录,也方便多版本共享数据。
const userDataPath = app.getPath('userData');
const settingsPath = path.join(userDataPath, 'settings.json');
(2)app.setPath(name, path)
允许你覆盖某些路径,例如将日志输出到自定义位置。谨慎使用,尤其不要随意覆盖 appData 等关键路径,以免造成混乱。
(3)app.getName() 和 app.getVersion()
返回应用的名称和版本号,分别取自 package.json 中的 name 和 version 字段。常用于窗口标题、关于对话框、发送错误报告时附带版本信息。
const appName = app.getName(); // "my-electron-app"
const version = app.getVersion(); // "1.0.0"
(4)app.quit() 和 app.exit(exitCode)
app.quit():优雅退出,会依次触发before-quit和will-quit事件,尝试关闭所有窗口。如果你在before-quit中异步操作阻止了退出,它可能无法立即退出。app.exit(exitCode):以指定的退出码立即退出,不会触发任何退出事件,直接终止进程。只应在极少数情况下使用(例如紧急错误恢复),否则可能导致数据丢失。
(5)app.relaunch() 和 app.isPackaged
app.relaunch():重启应用。通常结合app.quit()使用,在应用更新、语言切换等场景中自动重启。app.isPackaged:一个布尔值,判断应用是否处于打包后的运行环境。开发阶段为false,打包后为true。常用于区分开发/生产行为。
if (app.isPackaged) {
// 生产环境:不显示开发者工具
} else {
// 开发环境:允许打开 DevTools
}
7.1.3 全局配置与启动选项
在主进程脚本最顶部,你可以在 app 模块的 ready 事件之前设置一些影响整个应用行为的开关和命令行参数。这些配置必须在应用初始化前完成,否则可能不会生效。
(1)app.commandLine.appendSwitch(switch[, value])
向 Chromium 的命令行追加开关。例如,禁用 GPU 加速以解决某些显卡兼容性问题,或强制使用软件渲染:
app.commandLine.appendSwitch('disable-gpu');
app.commandLine.appendSwitch('disable-software-rasterizer');
注意:滥用此方法可能导致不可预知的副作用,仅在必要时添加,并做好平台兼容测试。
(2)app.disableHardwareAcceleration()
禁用硬件加速,使得整个应用使用 CPU 渲染。通常用于兼容老旧显卡或有图形渲染问题的机器。同样需要在 ready 之前调用。
app.disableHardwareAcceleration();
但这会显著降低图形性能,应作为最后的兼容性手段,不建议默认开启。
(3)app.setUserTasks(tasks) (Windows)
在 Windows 上,为应用设置“跳转列表”中的用户任务(Jump List)。这些任务会显示在任务栏图标右键菜单的“任务”区域。
app.setUserTasks([
{
program: process.execPath,
arguments: '--new-note',
iconPath: path.join(__dirname, 'note.ico'),
iconIndex: 0,
title: '新建笔记',
description: '快速创建一个新笔记'
}
]);
(4)app.setAsDefaultProtocolClient(protocol)
将你的应用注册为某个自定义协议的默认处理程序(如 myapp://)。在 macOS 和 Windows 上需要单独配置,但此方法可以简化注册流程。需要在 ready 后调用。
app.setAsDefaultProtocolClient('myapp');
(5)app.setAppUserModelId(id) (Windows)
在 Windows 上设置应用程序用户模型 ID,用于通知、任务栏分组等。必须在 ready 之前调用。
app.setAppUserModelId('com.mycompany.myapp');
(6)app.allowRendererProcessReuse (已废弃)
注意:较新版本的 Electron 中,渲染进程复用已经默认启用,此属性不再需要。
7.1.4 macOS 特有的行为与适配
macOS 的桌面生态有一些与其他平台不同的习惯,app 模块提供了相应的 API 来适配。
app.dock
控制程序坞图标上的操作。例如,设置图标弹跳、显示未读标记、动态菜单等。
app.dock.setBadge('3'); // 在图标右上角显示数字 3
app.dock.bounce(); // 弹跳图标(critical 模式连续弹跳)
需要小心使用,不要滥用通知标记以免骚扰用户。
app.hide()和app.show()
隐藏或显示所有应用窗口而不退出应用。在按 Command+H 隐藏应用时触发 'hide' 事件。
app.on('will-finish-launching')
在应用完成启动之前触发,适合在此注册 Apple Event 监听,处理 open-file、open-url 等事件。这些事件必须在 ready 之前注册才能接收系统传过来的信息。
app.on('will-finish-launching', () => {
app.on('open-file', (event, filePath) => {
// 处理从 Finder 拖拽或双击关联文件打开的文档
});
});
7.1.5 典型实战:应用启动完整流程
结合以上知识,下面展示一个标准 Electron 应用的主进程入口脚本结构,涵盖了生命周期管理、路径配置、窗口创建和安全实践。
// main.js
const { app, BrowserWindow, protocol } = require('electron');
const path = require('path');
// 在 app ready 之前的全局配置
app.disableHardwareAcceleration(); // 如需要
app.setAppUserModelId('com.example.myapp');
// 安全地定义协议(如果使用)
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('myapp', process.execPath, [path.resolve(process.argv[1])]);
}
}
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
});
mainWindow.loadFile('index.html');
}
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('before-quit', () => {
// 保存应用状态
console.log('保存最后状态...');
});
// 注册协议处理(deep link)
app.on('open-url', (event, url) => {
event.preventDefault();
// 解析 url 并传递到渲染进程
});
掌握 app 模块,你就能管理好 Electron 应用的“生老病死”。更重要的是,这些生命周期和全局配置是你实现自动更新、数据持久化、系统集成等功能的基础。在后续章节中,你会看到它们如何与具体业务逻辑紧密配合。