4. 主进程和渲染进程之间通信

主进程和渲染进程之间如何通信

在将主进程和渲染进程之间通信之前,大家可以先简单的理解一下什么是主进程,什么是渲染进程,他们之间的关系是什么?

主进程 & 渲染进程

主进程和渲染进程是Electron应用程序中的两个主要进程。

主进程:

  • 也称为浏览器进程或主进程。
  • 它控制着应用程序的生命周期,显示原生的GUI界面。
  • 它管理所有web页面和其他渲染进程。
  • 可以与操作系统进行交互,创建菜单,发送通知等。
  • 只有一个主进程。

渲染进程:

  • 也称为renderer进程或渲染进程。
  • 每个web页面运行在它自己的渲染进程里。
  • 负责运行和渲染HTML,CSS,JavaScript。
  • 无法直接访问原生GUI或操作系统。
  • 应用程序可以有多个渲染进程。

主进程和渲染进程之间的关系:

  • 主进程会创建一个或多个渲染进程。
  • 主进程与渲染进程之间使用IPC(跨进程通信)进行通信。
  • 主进程管理所有的页面和渲染进程。
  • 主进程可访问原生GUI,而渲染进程不可以。
  • 渲染进程负责页面的渲染,主进程负责应用程序的整体控制。

总结: 主进程控制整个应用程序,管理所有页面和渲染进程。渲染进程负责页面的渲染和运行JavaScript。主进程和渲染进程通过IPC通信。这种多进程架构提高了程序的性能、稳定性和安全性。

它们之间如何进行通信

主进程和渲染进程通过IPC通信, 在官方文档中的API里我们可以查阅到,主进程有个ipcMain, 渲染进程有个ipcRenderer, 我们可以使用它们来实现主进程和渲染进程之间的通信。

ipcMain

从主进程向渲染进程发送消息,查阅webContents.send获取更多信息。

  • 发送消息时,事件名称为channel 。
  • 回复同步信息时,需要设置event.returnValue。
  • 可以使用event.reply(...)将异步消息发送回发送者。 此方法将自动处理从非主 frame 发送的消息(比如: iframes)。相应的发送方法是: event.sender.send(...) 它将总是把消息发送到主 frame

IpcMain 模块有以下方法来侦听事件:

  1. ipcMain.on(channel, listener)​
  • channel string
  • listener Function

监听 channel, 当新消息到达,将通过 listener(event, args...) 调用 listener。

  1. ipcMain.once(channel, listener)​
  • channel string
  • listener Function

添加一次性 listener 函数。 这个 listener 只会在 channel下一次收到消息的时候被调用,之后这个监听器会被移除。

  1. ipcMain.removeListener(channel, listener)​
  • channel string
  • listener Function
    • ...args any\[\]

为特定的 channel 从监听队列中删除特定的 listener 监听者.

  1. ipcMain.removeAllListeners([channel])​
  • channel string (optional)

移除所有指定 channel 的监听器; 若未指定 channel,则移除所有监听器。

  1. ipcMain.handle(channel, listener)​
  • channel string
  • listener Function<Promise | any>

为一个 invokeable的IPC 添加一个handler。 每当一个渲染进程调用 ipcRenderer.invoke(channel, ...args) 时这个处理器就会被调用。如果 listener 返回一个 Promise,那么 Promise 的最终结果就是远程调用的返回值。 否则, 监听器的返回值将被用来作为应答值。

ipcMain.handleOnce(channel, listener)​

  • channel string
  • listener Function<Promise | any>

处理单个 invokeable 可触发的 IPC 消息,然后移除侦听器

  1. ipcMain.removeHandler(channel)​
  • channel string

移除 channel的所有处理程序,若存在

我们可以看到,关于ipcMain的监听函数分为on和handle, 那么它们的区别是什么呢, on是处理同步的,handle处理的是异步

ipcRenderer

介绍完主进程我们介绍一下渲染进程, ipcRenderer 模块使用以下方法来监听事件和发送消息。

  1. ipcRenderer.on(channel, listener)​

监听 channel, 当新消息到达,将通过 listener(event, args...) 调用 listener。

  1. ipcRenderer.once(channel, listener)​

添加一次性 listener 函数。 这个 listener 只会在 channel下一次收到消息的时候被调用,之后这个监听器会被移除。

  1. ipcRenderer.removeListener(channel, listener)​
  • channel string
  • listener Function
    • ...args any\[\]

为特定的 channel 从监听队列中删除特定的 listener 监听者.

  1. ipcRenderer.removeAllListeners(channel)​
  • channel string

移除所有的监听器,当指定 channel 时只移除与其相关的所有监听器。

  1. ipcRenderer.send(channel, ...args)​
  • channel string
  • ...args any\[\]

通过channel向主进程发送异步消息,可以发送任意参数。 参数将使用 Structured Clone Algorithm进行序列化,就像 window.postMessage,因此原型链将不会包含在内。 发送 Functions, Promises, Symbols, WeakMaps, 或 WeakSets 将抛出异常。注意: 发送非标准的 JavaScript 类型,如DOM 对象或特殊Electron 对象将会抛出异常。主线程不支持 DOM 对象比如 ImageBitmap, File, DOMMatrix 等对象, 它们无法通过Electron的IPC发送到主线程,主要原因是主线程无法对他们进行解码 若尝试通过IPC发送这种对象数据将返回异常

  1. ipcRenderer.invoke(channel, ...args)​
  • channel string
  • ...args any\[\]

Returns Promise<any> - Resolves 主进程返回值通过 channel 向主过程发送消息,并异步等待结果。 参数将使用 Structured Clone Algorithm进行序列化,就像 window.postMessage,因此原型链将不会包含在内。 发送 Functions, Promises, Symbols, WeakMaps, 或 WeakSets 将抛出异常

  1. ipcRenderer.sendSync(channel, ...args)​
  • channel string
  • ...args any\[\]

返回 any - 由 ipcMain 处理程序发送过来的值。

  1. ipcRenderer.postMessage(channel, message, [transfer])​
  • channel string
  • message any
  • transfer MessagePort\[\] (optional)

发送消息到主进程,同时可以选择性发送零到多个 MessagePort 对象从渲染进程发送到主进程的MessagePort对象可作为MessagePortMain对象访问触发事件的ports端口属性

  1. ipcRenderer.sendToHost(channel, ...args)​
  • channel string
  • ...args any\[\]

就像 ipcRenderer.send,不同的是消息会被发送到 host 页面上的 <webview> 元素,而不是主进程。

在渲染进程中,我们的方法发送方法很多,这里我推荐使用invoke 做异步通信,因为同步会造成进程阻塞。

试验

我们现在尝试一下渲染进程给主进程通信, 首先我们要在index.html中创建一个id为msgBtn 的button按钮, 在 /renderer/app.js 中我们去拿到这个按钮, 同时绑定一个点击事件, 这里需要注意,由于在渲染进程中我们获取不到 electron这个对象, 所以我们需要在 preLoad.js 中定义invoke挂载到window去使用,此时preLoad.js 代码如下:

arduino 复制代码
const { contextBridge, ipcRenderer } = require('electron')
const fs = require('path')

console.log(process.platform)
console.log(fs)

console.log(ipcRenderer)

function invoke(channel, someArgument) {
  return ipcRenderer.invoke(channel, someArgument)
}

contextBridge.exposeInMainWorld('callNode', {
  platform: process.platform,
  invoke,
})

暴露了一个invoke对象,是一个函数,用法与ipcRenderer.invoke一致,那么此时我们就可以在app.js中去调用这个它了, 通过channel向主进程发送异步消息,可以发送任意参数,我们就发送一个'hello, main' ,由于我们的 invoke 是异步的,它是一个promise 我们可以通过.then的方式拿到主进程返回给我们的消息

javascript 复制代码
console.log(window.callNode)

const msgBtn = document.querySelector('#msgBtn')

msgBtn.addEventListener('click', () => {
  window.callNode.invoke('send-event', 'hello main').then((res) => {
    console.log(res)
  })
})

目前渲染进程已经把消息发送出去了,我们该怎么接收呢,在主进程main.js中,我们通过 ipcMain去定义一个监听事件handle, 此时我们的main.js如下:

javascript 复制代码
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('node:path')

const createWindows = () => {
  const win = new BrowserWindow({
    width: 1280,
    height: 800,
    webPreferences: {
      nodeIntegration: true,
      preload: path.join(__dirname, 'preLoad.js'),
    },
  })

  win.loadFile('index.html')

  win.webContents.openDevTools()
}

app.whenReady().then(() => {
  createWindows()
  createWindows()
  // 仅 macOS 支持
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindows()
    }
  })
})

app.on('window-all-closed', () => {
  // 对于 Mac 系统, 关闭窗口时并不会直接退出应用, 此时需要我们来手动处理
  if (process.platform === 'darwin') {
    console.log('close')
    app.quit()
  }
})

ipcMain.handle('send-event', (event, data) => {
  console.log(data)
  return 'hello render'
})

我们通过handle回调拿到了渲染进程发送过来的消息数据, 同时通过 return 给渲染进程发送了一条消息, 点击按钮,此时就可以看到在bash中打印了'hello main', 同时窗口控制台也打印了'hello render', 那么到底, 我们的通信已经成功了

相关推荐
晓得迷路了2 小时前
栗子前端技术周刊第 148 期 - Turborepo 2.11、Chrome 154 iframe、Node.js 26...
前端·javascript·css
IT_陈寒2 小时前
Vite热更新失效?你可能漏了这个配置项
前端·人工智能·后端
10年前端老司机2 小时前
Next.js+LangGraph.js+ 简历工具AI Agent完整落地
前端·langchain·agent
IT_陈寒7 小时前
Java中equals方法比了个寂寞?原来这才是正确的重写姿势
前端·人工智能·后端
默_笙7 小时前
🚓 分诊台与拆题术:让 RAG 学会判断和规划
前端·javascript
CopyCode7 小时前
用 AI 迁项目有多爽?我把 Webpack 迁 Vite 的全过程记下来了
前端·架构
去伪存真7 小时前
Electron 自动化发布指南:GitHub Actions 跨平台打包全纪录
前端·electron
计算机魔术师7 小时前
OpenAI智能体失控闯进美国政府网站,53张用户图片外泄背后
前端
颜进强7 小时前
14 · NestJS ExecutionContext 执行上下文:守卫、拦截器、过滤器拿到的"同一个 context",为什么能力不一样?
前端·后端·ai编程
程序员Flycan7 小时前
🚀 跨域终结者:前端代理服务器(Proxy)原理解析与配置总结
前端