如果把 Electron 应用比作一个操作系统上的独立程序,那么主进程(Main Process)就是这个程序的“大脑”和“操作系统接口层”。每个 Electron 应用只有一个主进程,它不负责绘制界面,但掌控着应用的一切底层行为:何时启动、怎样创建窗口、如何响应系统事件、何时退出。理解主进程的三大核心职责,是掌握 Electron 开发的关键一步。
1. 应用生命周期管理
主进程的第一个任务,就是管理应用从启动到退出的完整生命周期。在代码层面,这一职责集中体现在 app 模块上,它暴露了一系列事件和方法,让你能够精确控制应用在 macOS、Windows、Linux 上的行为。
启动阶段
当用户双击应用图标或通过命令行启动时,主进程的入口脚本开始执行。你需要在 app.whenReady() 这个 Promise 完成之后才能创建窗口、注册菜单或执行其他系统操作。这是因为 Electron 需要一定时间来初始化 Chromium 和 Node.js 的内部状态,在此之前调用某些 API 会出错。
const { app, BrowserWindow } = require('electron');
app.whenReady().then(() => {
// 此时应用已就绪,可以安全创建窗口
const mainWindow = new BrowserWindow({ width: 1024, height: 768 });
mainWindow.loadFile('index.html');
});
运行与退出
应用运行期间,主进程会持续监听系统事件。比如在 macOS 上,用户点击 Dock 图标时如果没有窗口存在,通常应该重新创建一个窗口;应用被系统要求退出时,需要完成数据保存等清理工作。这些都可以通过 app 的事件来完成。
// macOS 典型行为:点击 Dock 图标时,如果没有窗口则新建一个
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// 当所有窗口都关闭时(Windows/Linux 上通常要退出应用)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// 在应用即将退出前的清理操作
app.on('before-quit', () => {
// 保存用户状态、关闭数据库连接等
});
通过这些生命周期钩子,你可以让应用的行为完美匹配对应操作系统的用户习惯,而不是生硬地把网页逻辑套在桌面壳子里。
2. 原生能力调度
主进程的第二个职责是安全地调度操作系统级别的原生能力。出于安全考虑,现代 Electron 应用通常会关闭渲染进程直接访问 Node.js 和 Electron API 的权限,只允许它们通过预加载脚本和 IPC(进程间通信)向主进程发送请求。于是,主进程就成了一个“权限代理”,所有敏感操作都集中在这里执行。
常见的原生能力包括:
- 系统对话框:打开文件、保存文件、消息提示。
- 系统菜单与右键菜单:
Menu和MenuItem模块。 - 系统托盘:
Tray模块,可以创建常驻通知区域的图标和菜单。 - 全局快捷键:
globalShortcut模块,即使应用窗口不在前台也能响应。 - 文件系统与系统信息:通过 Node.js 的
fs、path以及os模块完成。 - 电源状态与网络状态:
powerMonitor和net模块。
以“打开文件”这个最常见场景为例,安全流程是这样的:
- 渲染进程通过
ipcRenderer.invoke('dialog:openFile')向主进程发请求。 - 主进程在
ipcMain.handle('dialog:openFile', ...)中调用dialog.showOpenDialog(),弹出系统原生的文件选择框。 - 用户选择文件后,主进程将文件路径返回给渲染进程。
- 渲染进程收到路径,再通过另一条 IPC 请求去读取文件内容,由主进程用
fs.readFile完成并返回。
整个过程中,实际的系统调用全部发生在主进程,渲染进程只负责展示和请求,暴露的攻击面极小。
// 主进程(main.js)
const { ipcMain, dialog } = require('electron');
ipcMain.handle('dialog:openFile', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: 'Markdown', extensions: ['md'] }]
});
if (canceled) return null;
return filePaths[0];
});
这种集中式调度不仅安全,也让代码结构更清晰:所有涉及系统底层的逻辑都在主进程里,渲染进程则保持为一个干净的前端应用,方便调试和维护。
3. 窗口管理
主进程的第三个核心职责是创建和管理浏览窗口(BrowserWindow)。Electron 应用可以拥有一个或多个窗口,每创建一个 BrowserWindow 实例,底层就会启动一个新的渲染进程去承载网页内容。
窗口管理不仅仅是 new BrowserWindow() 一行代码,它涉及:
- 窗口配置:宽高、位置、是否可调整大小、是否有边框、是否透明、是否全屏、是否为模态窗口等。
- 多窗口协同:主窗口、设置窗口、预览窗口、弹窗……主进程需要统一创建、显示、隐藏、关闭这些窗口,并处理它们之间的消息转发。
- 窗口事件监听:当窗口被关闭、获得焦点、失去焦点、进入全屏状态改变时,主进程可以做出相应反应。
- 平台差异化:macOS 和 Windows 的窗口行为有所不同(例如 macOS 关闭窗口通常不退出应用),主进程需用
process.platform做条件处理。
一个典型的窗口创建函数可能长这样:
function createMainWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
title: 'My App',
icon: path.join(__dirname, 'assets/icon.png'),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, // 必须开启,保证安全
nodeIntegration: false // 禁止渲染进程直接用 Node
}
});
win.loadFile('index.html');
// 开发模式下可以打开 DevTools
if (process.env.NODE_ENV === 'development') {
win.webContents.openDevTools();
}
// 监听窗口关闭事件,做一些清理(比如通知主进程窗口列表更新)
win.on('closed', () => {
// 手动解除对该窗口的引用,让 GC 回收
// 一般会在多窗口应用中维护一个窗口数组
});
return win;
}
对于多窗口应用,主进程往往会维护一个窗口管理器,统一创建、销毁窗口,并处理好窗口间的父子关系或通信路由。例如,在一个设计工具中,可能会有主编辑窗口、工具面板窗口、预览窗口,用户关闭主窗口时应该同时关闭所有附属窗口,这些逻辑全部运行在主进程中。
把这三项职责合在一起看,主进程就是一个 Electron 应用的“后台管家”:它监听应用的生命周期事件,在合适的时机创建或销毁窗口;它手握所有原生能力的钥匙,却只通过安全的桥梁暴露给前端界面;它管理着每一个可见或隐藏的窗口,确保整个应用在多窗口场景下运转有序。掌握主进程的玩法,就等于掌握了 Electron 应用的控制中枢。