3. 主进程深度解析
好,咱们今天来聊聊主进程。这是 Electron 里最核心的概念之一,说白了,它就是整个应用的“大脑”。我刚开始接触 Electron 时,也搞不清主进程和渲染进程到底谁管谁,后来踩了几个坑才彻底明白。
主进程的定义与职责
主进程,就是 Electron 应用的入口。它运行在 Node.js 环境里,拥有完整的 Node.js API 权限。你可以把它想象成一个“大管家”,负责管理应用的生命周期、创建窗口、处理系统事件。
它的核心职责有这么几个:
- 管理应用生命周期:从启动到退出,全由主进程说了算
- 创建和管理窗口:每个 BrowserWindow 实例都由主进程创建
- 处理系统级事件:比如菜单栏、托盘、系统通知
- 调用原生 API:文件系统、进程管理、网络请求等
- 进程间通信(IPC):作为桥梁,连接渲染进程和系统资源
记住一点:主进程是唯一能直接操作原生 GUI 的进程。渲染进程只能通过 IPC 向主进程“请求”操作。
主进程的生命周期
主进程的生命周期,说白了就是 app 模块的“一生”。我习惯把它分成四个阶段:
| 阶段 | 触发事件 | 典型操作 |
|---|---|---|
| 准备阶段 | ready | 创建窗口、注册协议 |
| 运行阶段 | window-all-closed | 处理用户交互、IPC 通信 |
| 激活阶段 | activate | macOS 下重新显示窗口 |
| 退出阶段 | will-quit / quit | 清理资源、保存状态 |
嗯,这里要注意:ready 事件是必须等待的。在它触发之前,你不能创建任何窗口。我曾经犯过这个错——在 ready 之前就调用了 new BrowserWindow(),结果窗口死活出不来,调试了半天才发现是顺序问题。
个人经验:我习惯在 ready 事件里做所有初始化工作。这样能保证环境完全就绪,避免各种奇怪的竞态问题。
app 模块详解
app 模块是主进程的“心脏”。它控制着整个应用的行为。常用的方法我列一下:
app.whenReady():返回一个 Promise,推荐用这个替代 ready 事件监听app.quit():退出应用,会触发 will-quit 事件app.getPath(name):获取系统路径,比如 userData、exe 等app.setAppUserModelId(id):Windows 下设置任务栏图标分组app.dock:macOS 下操作 Dock 图标
举个例子,获取用户数据目录:
const { app } = require('electron')
app.whenReady().then(() => {
const userDataPath = app.getPath('userData')
console.log('用户数据目录:', userDataPath)
// 输出类似:/Users/你的用户名/Library/Application Support/你的应用名
})
你想想看,这个 userData 路径有多重要?用户的配置文件、缓存、数据库,都应该放在这里。我有个项目就是因为把数据写到了应用安装目录,结果更新时全丢了——嗯,从那以后我再也不敢乱放数据了。
BrowserWindow 的创建与管理
创建窗口是主进程最频繁的操作。new BrowserWindow(options) 就是干这个的。options 里有很多配置项,我挑几个重点说说:
| 配置项 | 作用 | 我的建议 |
|---|---|---|
| width / height | 窗口尺寸 | 别写死,用屏幕尺寸的百分比 |
| webPreferences | 渲染进程配置 | 一定要设置 nodeIntegration: false |
| frame | 是否显示标题栏 | 自定义标题栏时设为 false |
| resizable | 是否可调整大小 | 工具类窗口可以设为 false |
来看一个完整的窗口创建示例:
const { app, BrowserWindow } = require('electron')
let mainWindow
app.whenReady().then(() => {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false, // 安全第一
contextIsolation: true, // 隔离上下文
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
// 窗口关闭时释放引用
mainWindow.on('closed', () => {
mainWindow = null
})
})
避坑指南:我曾经在生产环境中忘了设置 contextIsolation: true,结果被安全团队打回来了。现在我的每个项目都默认开启上下文隔离,这能防止渲染进程直接访问 Node.js API,大大降低安全风险。
窗口管理还有个重要概念——窗口引用。JavaScript 的垃圾回收机制会回收没有引用的对象。所以你要把窗口实例保存在全局变量里,否则它会被自动销毁。我见过不少新手因为这个原因,窗口一闪就没了。
另外,多窗口场景下,我习惯用一个 Map 来管理所有窗口:
const windows = new Map()
function createWindow(id, options) {
const win = new BrowserWindow(options)
windows.set(id, win)
win.on('closed', () => {
windows.delete(id)
})
return win
}
这样管理起来清晰多了。你想想看,如果应用有十几个窗口,每个都用一个全局变量,那代码得多乱?
总结一下
主进程是 Electron 应用的基石。它负责启动、管理窗口、处理系统事件。我个人觉得,理解主进程的关键在于三点:
- 它是单线程的,所有操作都要考虑性能
- 它拥有最高权限,能调用所有原生 API
- 它是 IPC 通信的中枢,连接着渲染进程和系统资源