boxmoe_header_banner_img

Hello! 欢迎来到我的博客!

加载中

文章导读

快速上手Electron_主进程和渲染进程


avatar
xiaoifei 2025年5月5日 113
//主进程执行
const { app, BrowserWindow } = require('electron')

let mainWindow = null
const createWindow = () => {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: { 
      nodeIntegration: true, //渲染进程也有能力访问Node.js的API
      contextIsolation: false
    }
  })
  mainWindow.loadFile('index.html') // 加载渲染进程内容
}

app.whenReady().then(() => {
  createWindow()
  app.on('activate', () => { //MacOS监听开启窗口
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow()
    }
  })
  app.on('closed',()=>{ //监听关闭事件,负责回收
    mainWindow = null
  })
})
app.on('window-all-closed', () => { //监听关闭事件,负责退出
  if (process.platform !== 'darwin') app.quit()
})
  • 主进程负责完成监听应用程序的生命周期事件、 启动第一个窗口、加载index.html页面、应用程序关闭后回收资源、 退出程序等工作。
  • 渲染进程负责完成渲染界面、接收用户输入、响应用户的交互等工作

主要模块

主进程模块包括

模块名功能说明
app控制应用生命周期(启动、退出、事件监听)。
autoUpdater管理应用自动更新。
BrowserView创建和管理浏览器视图(嵌入在窗口中的子内容区域)。
BrowserWindow创建和控制浏览器窗口。
contentTracing收集 Chromium 内容模块的性能追踪数据。
dialog显示系统对话框(文件选择、消息提示等)。
globalShortcut注册全局快捷键。
ipcMain主进程与渲染进程间的通信(Inter-Process Communication)。
MenuItem定义菜单项(与 Menu 模块配合使用)。
net发起 HTTP/HTTPS 请求(替代 Node.js 的 http 模块)。
netLog记录网络请求日志。
Notification显示系统通知(桌面提醒)。
powerMonitor监听系统电源状态变化(如睡眠、唤醒)。
powerSaveBlocker阻止系统进入省电模式。
protocol注册自定义协议(如 app://)。
screen获取屏幕信息(分辨率、多显示器配置)。
session管理浏览器会话(缓存、Cookie、权限等)。
systemPreferences访问系统偏好设置(如主题、颜色)。
TouchBar为 macOS Touch Bar 创建交互控件。
Tray创建和管理系统托盘图标。
webContents控制窗口的网页内容(加载页面、执行脚本等)。

渲染进程模块包括

模块名功能说明
desktopCapturer捕获屏幕或窗口内容(用于屏幕共享)。
ipcRenderer渲染进程与主进程间的通信(发送/接收消息)。
remote (已废弃)在渲染进程中调用主进程模块(Electron 14+ 已废弃,推荐改用 ipcRenderer)。
webFrame控制网页的渲染行为(缩放、CSS 注入等)。

公用模块包括

模块名功能说明
clipboard读写剪贴板内容(文本、图片等)。
crashReporter收集应用崩溃报告。
nativeImage处理本地图片(如 PNG、JPEG)的创建和转换。
shell调用系统默认应用(如打开文件、链接)。

进程通信

为什么废弃 remote

  1. 性能问题remote 模块会在渲染进程和主进程之间进行同步操作,这可能会导致性能问题,尤其是在高频调用时。
  2. 安全性问题remote 会将主进程的功能暴露给渲染进程,增加了安全风险。
  3. 维护成本高remote 的实现复杂,难以维护。

替代方案:使用 IPC 通信
通过 ipcMainipcRenderer 可以实现主进程和渲染进程的交互:

渲染进程与主进程间发送消息

在官方实例中分为单向和双向
首先来介绍一下单向(渲染进程向主进程发送)
在渲染进程触发事件的时候执行下面代码

案例:设置窗口标题(渲染进程->主进程)

利用单向数据通信实现从渲染窗口获得数据传入主进程通过调用窗口提供的setTitleAPI设置窗口标题
index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>Hello from Electron renderer!</title>
  </head>
  <body>
    Title: <input id="title" placeholder="placeholder"/>
    <button id="btn" type="button">Set</button>
    <script src="./renderer.js"></script>
  </body>
</html>

renderer.js

document.getElementById('btn').addEventListener('click',function(){
  const title = document.getElementById('title').value
  window.electronAPI.setTitle(title)
})

preload.js

const { contextBridge, ipcRenderer } = require('electron');
// 通过 contextBridge 安全暴露 IPC 方法
contextBridge.exposeInMainWorld('electronAPI', {
  setTitle: (title) => ipcRenderer.send('set-title', title),
});

main.js

ipcMain.on('set-title',(event, title)=>{
  const webContents = event.sender
  const win = BrowserWindow.fromWebContents(webContents)
  win.setTitle(title)
})

主要步骤如下:

  1. 在渲染进程调用preload暴露的接口setTitle,将数据通过参数传递
  2. preload中通过ipcRenderer.send单向发送
  3. 在主线程中使用ipcMain.on来监听
  4. 通过event.sender获得上下文,再通过上下文获得窗口实例,最后通过setTitle设置title

event对象格式如下
IpcMainEvent 对象继承 Event | Electron

  • type String – Possible values include frame
  • processId Integer – 发送该消息的渲染进程内部的ID
  • frameId Integer – 发送该消息的渲染进程框架的ID(可能是iframe)
  • returnValue any – 如果对此赋值,则该值会在同步消息中返回
  • sender WebContents – Returns the webContents that sent the message
  • senderFrame WebFrameMain | null Readonly – The frame that sent this message. May be null if accessed after the frame has either navigated or been destroyed.
  • ports MessagePortMain[] – A list of MessagePorts that were transferred with this message
  • reply Function – 将 IPC 消息发送到渲染器框架的函数,该渲染器框架发送当前正在处理的原始消息。 您应该使用“reply”方法回复发送的消息,以确保回复将转到正确的进程和框架。
    • channel string
    • ...args any[]

案例:展示选中文件路径(渲染进程<->主进程)

传递事件调用出系统API的文件选择器,然后返回文件选择器选中的文件路径
常见于渲染器进程代码调用主进程模块并等待结果
index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>Hello from Electron renderer!</title>
  </head>
  <body>
    <button type="button" id="btn">Open a File</button>  
    File path: <strong id="filePath"></strong>  
    <script src='./renderer.js'></script>
  </body>
</html>

renderer.js

const filePathElement = document.getElementById('filePath')
document.getElementById('btn').addEventListener('click',async function(){
  const filePath = await window.electronAPI.openFile()
  filePathElement.innerText = filePath
})

preload.js

const { contextBridge, ipcRenderer } = require('electron');
// 通过 contextBridge 安全暴露 IPC 方法
contextBridge.exposeInMainWorld('electronAPI', {
  openFile: () => ipcRenderer.invoke('dialog:openFile'),
});

main.js

async function handleFileOpen () {
  const { canceled, filePaths } = await dialog.showOpenDialog()
  if (!canceled) {
    return filePaths[0]
  }else{
    return ""
  }
}
ipcMain.handle('dialog:openFile', handleFileOpen)

主要步骤如下:

  1. 在渲染进程调用preload暴露的接口openFile,将数据通过参数传递
  2. preload中通过ipcRenderer.invoke绑定到dialog:openFile通道
  3. 在主线程中使用ipcMain.handle来进行通道监听
  4. 最后return返回路径字符串

还有一种为了兼容而使用on和reply的实现方法,在官网有说明,这里不过多赘述,因为官网推崇的是invoke的写法

案例:数字计数器(渲染进程<-主进程)

菜单的注册需要在主进程中实现,因此消息的传递也是从主进程开始
由主进程主动发送消息update-counter
此时为了渲染进程能够接收,需要在payload设置监听
index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>Hello from Electron renderer!</title>
  </head>
  <body>
    Current value: <strong id="counter">0</strong>
    <script src="./renderer.js"></script>
  </body>
</html>

main.js

const createWindow = () => {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: { 
      nodeIntegration: false, // 禁用 Node.js 集成(默认安全配置)
      contextIsolation: true, // 启用上下文隔离
      preload: path.join(__dirname, 'preload.js') // 指定预加载脚本
    }
  })
  const menu = Menu.buildFromTemplate([
    {
      label: app.name,
      submenu: [
        {
          click: () => mainWindow.webContents.send('update-counter', 1),
          label: 'Increment'
        },
        {
          click: () => mainWindow.webContents.send('update-counter', -1),
          label: 'Decrement'
        }
      ]
    }
  ])
  Menu.setApplicationMenu(menu)
  mainWindow.loadFile('index.html')
  mainWindow.webContents.openDevTools() // 开启渲染调试工具
}

renderer.js

const counter = document.getElementById('counter')

window.electronAPI.onUpdateCounter((value) => {
  const oldValue = Number(counter.innerText)
  const newValue = oldValue + value
  counter.innerText = newValue.toString()
  window.electronAPI.counterValue(newValue) // 回调(可选)
})

preload.js

const { contextBridge, ipcRenderer } = require('electron');

// 通过 contextBridge 安全暴露 IPC 方法
contextBridge.exposeInMainWorld('electronAPI', {
  onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
  counterValue: (value) => ipcRenderer.send('counter-value', value) // 可选
});

主要步骤如下:

  1. 在主进程使用mainWindow.webContents.send发送update-counter事件同时传递一个参数
  2. preload中有一个方法onUpdateCounter提供给渲染进程,渲染进程需要传入一个callback来接收主进程传递的参数
  3. 渲染进程调用electronAPI.onUpdateCounter传入匿名函数实现回调,回调中又通过preload又调用了counterValue
  4. preload的counterValue被调用,在传递一个消息给主进程,于是实现了主进程->渲染进程的双向数据通信

有些对象无法通过消息传递,根据官网查询如下:

特别是 DOM 对象(例如 ElementLocation 和 DOMMatrix),Node.js 中由 C++ 类支持的对象(例如 process.envStream 的一些成员)和 Electron 中由 C++ 类支持的对象(例如 WebContentsBrowserWindow 和 WebFrame)无法使用结构化克隆序列化。

渲染进程之间的消息传递

  1. 方法1:如果一个程序有多个窗口,并要在窗口之间传递消息,可以通过 主进程中转,即窗口A先把消息发送给主进程,主进程再把这个消息发 送给窗口B,这就完成了窗口A和窗口B的通信。因为窗口的创建工作往 往是由主进程完成的,所以主进程持有所有窗口的实例,通过主进程中转窗口间消息非常常见
  2. 方法2:使用ipcRenderer.sendTo,如果你在窗口A的渲染进程中知道窗口B的webContents的id,就 可以直接从窗口A发送消息给窗口B


评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码