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)

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

ipcMain.handleOnce(channel, listener)

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

  1. ipcMain.removeHandler(channel)
  • channel string

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

我们可以看到,关于ipcMain的监听函数分为onhandle, 那么它们的区别是什么呢, 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为msgBtnbutton按钮, 在 /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 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。3 小时前
案例-表白墙简单实现
前端·javascript·css
数云界3 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd3 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常3 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer3 小时前
Vite:为什么选 Vite
前端
小御姐@stella3 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing3 小时前
【React】增量传输与渲染
前端·javascript·面试
GISer_Jing3 小时前
WebGL在低配置电脑的应用
javascript
eHackyd3 小时前
前端知识汇总(持续更新)
前端