Appearance
第3章:Electron 核心架构与主进程、渲染进程
3.1 Electron 核心架构
Electron 的核心架构由两个主要部分组成:主进程(Main Process)和渲染进程(Renderer Process)。这种架构设计使得 Electron 能够同时利用 Node.js 的能力和浏览器的渲染能力。
3.1.1 架构组成
主进程:
- 运行在 Node.js 环境中
- 负责应用的生命周期管理
- 创建和管理浏览器窗口
- 访问原生系统 API
- 处理与操作系统的交互
渲染进程:
- 运行在 Chromium 浏览器环境中
- 负责页面的渲染和用户交互
- 每个窗口对应一个渲染进程
- 可以使用前端技术(HTML/CSS/JavaScript)
预加载脚本(Preload Script):
- 运行在渲染进程中,但具有 Node.js 环境的访问权限
- 用于在渲染进程和主进程之间建立安全的通信桥梁
3.2 主进程(Main Process)
3.2.1 作用与职责
应用生命周期控制:
- 应用的启动、退出
- 窗口的创建、管理
- 应用级别的事件处理
原生功能访问:
- 文件系统操作
- 系统通知
- 系统菜单和托盘
- 原生对话框
资源管理:
- 管理应用的全局资源
- 处理应用的更新
- 管理多个渲染进程
3.2.2 核心模块
- app:控制应用的生命周期
- BrowserWindow:创建和管理浏览器窗口
- ipcMain:处理来自渲染进程的消息
- Menu:创建应用菜单
- Tray:创建系统托盘图标
- dialog:显示原生对话框
- Notification:显示系统通知
3.3 渲染进程(Renderer Process)
3.3.1 作用与职责
页面渲染:
- 渲染 HTML/CSS 页面
- 处理页面的布局和样式
用户交互:
- 处理用户的鼠标、键盘事件
- 响应用户的操作
- 与页面元素交互
前端逻辑:
- 执行前端 JavaScript 代码
- 处理页面的业务逻辑
- 与主进程通信
3.3.2 核心模块
- ipcRenderer:与主进程通信
- webContents:控制网页内容
- remote:(已废弃)访问主进程模块
3.4 主进程与渲染进程的区别
| 特性 | 主进程 | 渲染进程 |
|---|---|---|
| 运行环境 | Node.js | Chromium 浏览器 |
| 权限 | 完全访问系统 API | 有限制的访问权限 |
| 通信方式 | ipcMain | ipcRenderer |
| 生命周期 | 与应用相同 | 与窗口相同 |
| 数量 | 只有一个 | 每个窗口一个 |
| 核心模块 | app, BrowserWindow 等 | ipcRenderer, webContents 等 |
| DOM 访问 | 无 | 有 |
| Node.js API | 完全访问 | 默认受限,需配置 |
3.5 进程间通信(IPC)基础
3.5.1 为什么需要 IPC?
由于主进程和渲染进程运行在不同的环境中,它们之间需要一种安全的通信机制来交换数据和触发操作。IPC(Inter-Process Communication)就是解决这个问题的机制。
3.5.2 IPC 核心模块
- ipcMain:在主进程中使用,用于监听来自渲染进程的消息
- ipcRenderer:在渲染进程中使用,用于向主进程发送消息和接收主进程的回复
3.5.3 通信方式
单向通信:
- 渲染进程 → 主进程
- 主进程 → 渲染进程
双向通信:
- 请求-响应模式
- 主进程和渲染进程之间的双向数据交换
3.6 实操案例:简单 IPC 通信
3.6.1 场景描述
创建一个简单的 Electron 应用,实现以下功能:
- 渲染进程向主进程发送消息
- 主进程接收消息并处理
- 主进程向渲染进程回复消息
- 渲染进程接收回复并显示
3.6.2 实现步骤
- 修改 main.js(主进程)
javascript
const { app, BrowserWindow, ipcMain } = require('electron')
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
})
win.loadFile('index.html')
win.webContents.openDevTools()
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// 监听来自渲染进程的消息
ipcMain.on('message-from-renderer', (event, data) => {
console.log('主进程收到消息:', data)
// 处理消息
const response = `主进程已收到: ${data}`
// 回复渲染进程
event.sender.send('message-from-main', response)
})
// 处理渲染进程的请求
ipcMain.handle('request-to-main', async (event, data) => {
console.log('主进程收到请求:', data)
// 模拟异步处理
await new Promise(resolve => setTimeout(resolve, 1000))
// 返回处理结果
return `主进程处理结果: ${data}`
})- 修改 index.html(渲染进程)
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>IPC 通信示例</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
padding: 0;
}
h1 {
color: #333;
}
.container {
margin: 20px 0;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
button {
padding: 10px 20px;
margin: 5px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
#response {
margin-top: 10px;
padding: 10px;
background-color: #f0f0f0;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>IPC 通信示例</h1>
<div class="container">
<h2>单向通信</h2>
<button id="send-message">发送消息到主进程</button>
<div id="response"></div>
</div>
<div class="container">
<h2>双向通信</h2>
<button id="send-request">向主进程发送请求</button>
<div id="request-response"></div>
</div>
<script>
const { ipcRenderer } = require('electron')
// 发送消息到主进程
document.getElementById('send-message').addEventListener('click', () => {
const message = 'Hello from renderer process!' + new Date().toLocaleString()
ipcRenderer.send('message-from-renderer', message)
console.log('渲染进程发送消息:', message)
})
// 监听主进程的回复
ipcRenderer.on('message-from-main', (event, data) => {
console.log('渲染进程收到回复:', data)
document.getElementById('response').textContent = data
})
// 向主进程发送请求
document.getElementById('send-request').addEventListener('click', async () => {
const requestData = 'Request data ' + new Date().toLocaleString()
console.log('渲染进程发送请求:', requestData)
try {
// 等待主进程的响应
const response = await ipcRenderer.invoke('request-to-main', requestData)
console.log('渲染进程收到响应:', response)
document.getElementById('request-response').textContent = response
} catch (error) {
console.error('请求失败:', error)
}
})
</script>
</body>
</html>3.6.3 运行效果
启动应用:
bashnpm start测试单向通信:
- 点击 "发送消息到主进程" 按钮
- 观察控制台输出
- 查看页面上显示的回复
测试双向通信:
- 点击 "向主进程发送请求" 按钮
- 观察控制台输出
- 查看页面上显示的响应
3.7 新手易错点
3.7.1 混淆主进程与渲染进程
错误表现:
- 在渲染进程中尝试使用主进程的模块
- 在主进程中尝试访问 DOM
解决方案:
- 明确区分主进程和渲染进程的职责
- 使用 IPC 进行进程间通信
- 查阅官方文档,了解哪些模块属于哪个进程
3.7.2 IPC 通信失败排查
错误表现:
- 消息发送但未收到
- 消息接收但处理错误
- 双向通信超时
解决方案:
- 检查事件名称是否一致
- 检查消息处理函数是否正确
- 使用 try-catch 捕获错误
- 在控制台查看错误信息
- 确保渲染进程正确加载了 ipcRenderer
3.7.3 安全问题
错误表现:
- 渲染进程直接访问 Node.js API
- IPC 通信未进行数据验证
解决方案:
- 使用 contextIsolation: true 提高安全性
- 使用预加载脚本进行安全的 IPC 通信
- 对 IPC 消息进行数据验证
- 遵循 Electron 安全最佳实践
3.8 小结
通过本章的学习,你已经了解了 Electron 的核心架构和进程间通信机制:
- Electron 采用主进程和渲染进程的架构
- 主进程负责应用生命周期和原生功能
- 渲染进程负责页面渲染和用户交互
- 通过 IPC 机制实现进程间通信
- 掌握了基本的 IPC 通信方法
这些知识是 Electron 开发的核心基础,为你后续学习窗口操作、原生功能访问等高级特性打下了基础。
