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 应用的基石。它负责启动、管理窗口、处理系统事件。我个人觉得,理解主进程的关键在于三点:

  1. 它是单线程的,所有操作都要考虑性能
  2. 它拥有最高权限,能调用所有原生 API
  3. 它是 IPC 通信的中枢,连接着渲染进程和系统资源