人人都会AI编程

7.1 app 模块:应用生命周期、事件、全局配置

更新时间:2026-07-11

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-quitwill-quit 中。

事件的典型触发顺序(正常退出):

  1. 用户关闭最后一个窗口
  2. window-all-closed
  3. (如果你调用了 app.quit()before-quit
  4. 窗口关闭,其他进程退出
  5. will-quit
  6. 应用退出

在开发中,最常用的模式是在 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 中的 nameversion 字段。常用于窗口标题、关于对话框、发送错误报告时附带版本信息。

const appName = app.getName();  // "my-electron-app"
const version = app.getVersion(); // "1.0.0"

(4)app.quit()app.exit(exitCode)

  • app.quit():优雅退出,会依次触发 before-quitwill-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-fileopen-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 应用的“生老病死”。更重要的是,这些生命周期和全局配置是你实现自动更新、数据持久化、系统集成等功能的基础。在后续章节中,你会看到它们如何与具体业务逻辑紧密配合。