Skip to content

第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.jsChromium 浏览器
权限完全访问系统 API有限制的访问权限
通信方式ipcMainipcRenderer
生命周期与应用相同与窗口相同
数量只有一个每个窗口一个
核心模块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 通信方式

  1. 单向通信

    • 渲染进程 → 主进程
    • 主进程 → 渲染进程
  2. 双向通信

    • 请求-响应模式
    • 主进程和渲染进程之间的双向数据交换

3.6 实操案例:简单 IPC 通信

3.6.1 场景描述

创建一个简单的 Electron 应用,实现以下功能:

  • 渲染进程向主进程发送消息
  • 主进程接收消息并处理
  • 主进程向渲染进程回复消息
  • 渲染进程接收回复并显示

3.6.2 实现步骤

  1. 修改 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}`
})
  1. 修改 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 运行效果

  1. 启动应用

    bash
    npm start
  2. 测试单向通信

    • 点击 "发送消息到主进程" 按钮
    • 观察控制台输出
    • 查看页面上显示的回复
  3. 测试双向通信

    • 点击 "向主进程发送请求" 按钮
    • 观察控制台输出
    • 查看页面上显示的响应

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 开发的核心基础,为你后续学习窗口操作、原生功能访问等高级特性打下了基础。

© 2026 编程马·菜鸟教程 版权所有