主进程与渲染进程 #
一、进程模型概述 #
1.1 Electron 进程架构 #
text
┌─────────────────────────────────────────────────────────────┐
│ Electron 应用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ 主进程 │ ← 唯一,应用入口 │
│ │ (Main) │ │
│ │ │ │
│ │ - Node.js 环境 │ │
│ │ - 管理窗口 │ │
│ │ - 系统集成 │ │
│ │ - 原生 API │ │
│ └────────┬────────┘ │
│ │ │
│ │ 创建 │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 渲染进程 1 │ │ 渲染进程 2 │ ← 多个,每个窗口一个 │
│ │ (Renderer) │ │ (Renderer) │ │
│ │ │ │ │ │
│ │ - Chromium 环境 │ │ - Chromium 环境 │ │
│ │ - Web 页面渲染 │ │ - Web 页面渲染 │ │
│ │ - 用户交互 │ │ - 用户交互 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
1.2 进程对比 #
| 特性 | 主进程 | 渲染进程 |
|---|---|---|
| 数量 | 唯一 | 可多个 |
| 运行环境 | Node.js | Chromium |
| 入口文件 | package.json 中的 main | BrowserWindow 加载的页面 |
| 主要职责 | 管理应用生命周期、窗口 | 渲染页面、处理交互 |
| 可访问 API | 所有 Electron API | 部分 Electron API |
| DOM 访问 | 不可访问 | 可访问 |
二、主进程详解 #
2.1 主进程职责 #
javascript
// main.js - 主进程入口
const { app, BrowserWindow, ipcMain, Menu, Tray } = require('electron');
// 1. 管理应用生命周期
app.whenReady().then(() => {
createWindow();
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// 2. 创建和管理窗口
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600
});
win.loadFile('index.html');
}
// 3. 处理 IPC 通信
ipcMain.handle('get-app-path', () => {
return app.getAppPath();
});
// 4. 创建原生 UI 元素
function createMenu() {
const menu = Menu.buildFromTemplate([
{ label: '文件', submenu: [{ role: 'quit' }] }
]);
Menu.setApplicationMenu(menu);
}
// 5. 系统集成
function createTray() {
const tray = new Tray('icon.png');
tray.setToolTip('我的应用');
}
2.2 主进程可访问的模块 #
| 模块 | 说明 |
|---|---|
| app | 应用生命周期控制 |
| BrowserWindow | 窗口管理 |
| ipcMain | IPC 通信(主进程端) |
| Menu | 应用菜单 |
| Tray | 系统托盘 |
| dialog | 原生对话框 |
| Notification | 系统通知 |
| shell | 系统集成 |
| session | 会话管理 |
| net | 网络请求 |
| clipboard | 剪贴板 |
| globalShortcut | 全局快捷键 |
| powerMonitor | 电源监控 |
| screen | 屏幕信息 |
| nativeTheme | 系统主题 |
2.3 主进程最佳实践 #
javascript
// 1. 单例模式确保只有一个实例
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
});
}
// 2. 窗口引用管理
const windows = new Set();
function createWindow() {
const win = new BrowserWindow({});
windows.add(win);
win.on('closed', () => {
windows.delete(win);
});
}
// 3. 错误处理
process.on('uncaughtException', (error) => {
console.error('未捕获的异常:', error);
});
// 4. 优雅退出
let isQuitting = false;
app.on('before-quit', () => {
isQuitting = true;
});
function closeWindow(win) {
if (isQuitting) {
win.destroy();
} else {
win.hide();
}
}
三、渲染进程详解 #
3.1 渲染进程职责 #
javascript
// renderer.js - 渲染进程脚本
// 1. DOM 操作
document.getElementById('btn').addEventListener('click', () => {
console.log('按钮被点击');
});
// 2. UI 渲染
function renderList(items) {
const list = document.getElementById('list');
list.innerHTML = items.map(item => `<li>${item}</li>`).join('');
}
// 3. 用户交互
document.querySelector('form').addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(e.target);
// 处理表单数据
});
// 4. 通过 IPC 与主进程通信
const { ipcRenderer } = require('electron');
async function getAppPath() {
const path = await ipcRenderer.invoke('get-app-path');
return path;
}
3.2 渲染进程可访问的模块 #
| 模块 | 说明 |
|---|---|
| ipcRenderer | IPC 通信(渲染进程端) |
| desktopCapturer | 桌面捕获 |
| contextBridge | 上下文桥接(预加载脚本) |
| webFrame | Web 框架控制 |
3.3 渲染进程安全配置 #
javascript
// main.js - 创建窗口时的安全配置
const mainWindow = new BrowserWindow({
webPreferences: {
// 安全配置
nodeIntegration: false, // 禁用 Node.js 集成
contextIsolation: true, // 启用上下文隔离
enableRemoteModule: false, // 禁用 remote 模块
sandbox: true, // 启用沙箱
webSecurity: true, // 启用 Web 安全
// 预加载脚本
preload: path.join(__dirname, 'preload.js')
}
});
四、进程间通信 #
4.1 IPC 通信模型 #
text
┌─────────────────┐ ┌─────────────────┐
│ 渲染进程 │ │ 主进程 │
│ │ │ │
│ ipcRenderer │ │ ipcMain │
│ │ │ │
│ send() ──────►│ IPC Channel │◄─────── on() │
│ │ ────────────────►│ │
│ ◄───── on() │ │ invoke() ◄────│
│ │ │ │
│ invoke() ─────►│ │◄─── handle() │
│ │ │ │
└─────────────────┘ └─────────────────┘
4.2 基本通信方式 #
方式一:send/on 模式
javascript
// 渲染进程发送
ipcRenderer.send('channel-name', data);
// 主进程接收
ipcMain.on('channel-name', (event, data) => {
console.log('收到数据:', data);
// 回复消息
event.reply('channel-name-reply', responseData);
});
// 渲染进程接收回复
ipcRenderer.on('channel-name-reply', (event, data) => {
console.log('收到回复:', data);
});
方式二:invoke/handle 模式(推荐)
javascript
// 主进程:注册处理器
ipcMain.handle('get-user-info', async (event, userId) => {
const user = await fetchUserInfo(userId);
return user;
});
// 渲染进程:调用并获取结果
const user = await ipcRenderer.invoke('get-user-info', 123);
console.log('用户信息:', user);
4.3 预加载脚本桥接 #
javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// 安全地暴露 API 给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 获取用户信息
getUserInfo: (userId) => ipcRenderer.invoke('get-user-info', userId),
// 发送消息
sendMessage: (message) => ipcRenderer.send('send-message', message),
// 监听消息
onMessage: (callback) => {
ipcRenderer.on('receive-message', (event, data) => callback(data));
},
// 移除监听
removeListener: (callback) => {
ipcRenderer.removeListener('receive-message', callback);
}
});
javascript
// renderer.js - 使用暴露的 API
const userInfo = await window.electronAPI.getUserInfo(123);
window.electronAPI.onMessage((data) => {
console.log('收到消息:', data);
});
五、多窗口管理 #
5.1 多窗口架构 #
text
┌─────────────────────────────────────────┐
│ 主进程 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 窗口 1 │ │ 窗口 2 │ │ 窗口 3 │ │
│ │ 主窗口 │ │ 设置窗口 │ │ 关于窗口 │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
└───────┼───────────┼───────────┼─────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│渲染进程1│ │渲染进程2│ │渲染进程3│
└─────────┘ └─────────┘ └─────────┘
5.2 窗口管理器实现 #
javascript
// electron/main/windowManager.js
const { BrowserWindow } = require('electron');
const path = require('path');
class WindowManager {
constructor() {
this.windows = new Map();
}
createWindow(name, options) {
const win = new BrowserWindow({
...options,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
nodeIntegration: false,
contextIsolation: true
}
});
this.windows.set(name, win);
win.on('closed', () => {
this.windows.delete(name);
});
return win;
}
getWindow(name) {
return this.windows.get(name);
}
closeWindow(name) {
const win = this.windows.get(name);
if (win) {
win.close();
}
}
closeAllWindows() {
for (const win of this.windows.values()) {
win.close();
}
}
broadcast(channel, data) {
for (const win of this.windows.values()) {
win.webContents.send(channel, data);
}
}
}
module.exports = new WindowManager();
5.3 窗口间通信 #
javascript
// 主进程:转发消息到其他窗口
ipcMain.on('broadcast-message', (event, data) => {
const senderWin = BrowserWindow.fromWebContents(event.sender);
for (const [name, win] of windowManager.windows) {
if (win !== senderWin) {
win.webContents.send('receive-broadcast', data);
}
}
});
// 渲染进程:发送广播
ipcRenderer.send('broadcast-message', {
type: 'update',
data: { key: 'value' }
});
// 渲染进程:接收广播
ipcRenderer.on('receive-broadcast', (event, data) => {
console.log('收到广播:', data);
});
六、进程生命周期 #
6.1 主进程生命周期 #
text
┌─────────────┐
│ app.ready │ 应用准备就绪
└──────┬──────┘
│
▼
┌─────────────┐
│ 创建窗口 │
└──────┬──────┘
│
▼
┌─────────────┐
│ app.activate│ macOS 激活(点击 Dock 图标)
└──────┬──────┘
│
▼
┌─────────────────────┐
│ window-all-closed │ 所有窗口关闭
└──────┬──────────────┘
│
▼
┌─────────────┐
│ app.quit │ 应用退出
└─────────────┘
6.2 渲染进程生命周期 #
javascript
// 页面加载完成
window.addEventListener('DOMContentLoaded', () => {
console.log('DOM 加载完成');
});
// 页面完全加载
window.addEventListener('load', () => {
console.log('页面加载完成');
});
// 页面即将卸载
window.addEventListener('beforeunload', (e) => {
e.returnValue = '确定要离开吗?';
});
// 页面卸载
window.addEventListener('unload', () => {
console.log('页面卸载');
});
七、进程调试 #
7.1 主进程调试 #
方式一:控制台输出
javascript
console.log('主进程日志');
console.error('错误信息');
方式二:VS Code 调试
json
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"args": ["."],
"outputCapture": "std"
}
]
}
方式三:Chrome DevTools
bash
# 启动时开启调试端口
electron . --inspect=5858
# 然后在 Chrome 中访问
# chrome://inspect
7.2 渲染进程调试 #
javascript
// 在主进程中打开开发者工具
mainWindow.webContents.openDevTools();
// 或使用快捷键
// Windows/Linux: Ctrl + Shift + I
// macOS: Cmd + Option + I
八、性能优化 #
8.1 进程优化 #
javascript
// 1. 延迟加载窗口
function createWindow() {
const win = new BrowserWindow({
show: false // 先不显示
});
win.loadFile('index.html');
// 页面加载完成后再显示
win.once('ready-to-show', () => {
win.show();
});
}
// 2. 窗口复用
let settingsWindow = null;
function openSettings() {
if (settingsWindow) {
settingsWindow.focus();
return;
}
settingsWindow = new BrowserWindow({});
settingsWindow.on('closed', () => {
settingsWindow = null;
});
}
// 3. 减少渲染进程数量
// 使用 <webview> 标签代替多窗口
8.2 内存管理 #
javascript
// 及时清理不需要的窗口引用
let mainWindow = null;
function createWindow() {
mainWindow = new BrowserWindow({});
mainWindow.on('closed', () => {
mainWindow = null; // 清理引用
});
}
// 清理事件监听器
ipcRenderer.on('some-event', handler);
// 不需要时移除
ipcRenderer.removeListener('some-event', handler);
九、总结 #
9.1 核心要点 #
| 要点 | 说明 |
|---|---|
| 主进程 | 唯一,管理应用生命周期和窗口 |
| 渲染进程 | 多个,负责页面渲染和用户交互 |
| IPC 通信 | 主进程与渲染进程之间的桥梁 |
| 预加载脚本 | 安全地暴露 API 给渲染进程 |
9.2 下一步 #
现在你已经理解了 Electron 的进程模型,接下来让我们学习 进程间通信IPC,深入掌握进程通信的各种方式!
最后更新:2026-03-28