学习 Electron - 进程通信

Electron 进程通信

Electron 的主进程和渲染进程有着清楚分工且不可互换。

因此,从渲染进程直接访问 Node 接口或 从主进程访问 DOM 都是不可能的。

解决此问题的方法就是使用 进程间通信(IPC,inter-process communication)。

渲染器进程到主进程(单向)

使用 ipcRenderer.send API,然后使用 ipcMain.on 接受。

常用于从 Web 内容调用主进程 API。

// preload.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  setTitle: (title) => ipcRenderer.send('set-title', title)
})

// main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

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

  mainWindow.loadFile('index.html')
}

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()
})

// renderer.js
const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
  const title = titleInput.value
  window.electronAPI.setTitle(title)
})

渲染器进程到主进程(双向)

常应用于渲染器进程调用主进程模块并等待结果。

通过 ipcMain 和 ipcRenderer 模块进行进程间通信。

从渲染进程向主进程发送消息,可使用 ipcMain.handle 设置一个主进程处理程序,然后再预加载脚本中暴露一个 ipcRedner.invoke 的函数触发该 handle。

// preload.js
const { contextBridge, ipcRenderer } = require('electron')

context.exposeInMainWorld('versions', {
  ...,
  // 此处使用辅助函数包裹 ipcRenderer.invoke('ping') 调用
  // 是因为这样会让渲染器直接向主进程发送任意的 IPC 信息,导致问题
  ping: () => ipcRenderer.invoke('ping')
})

// main.js

const { app, BorwserWindow, ipcMain } = require('electron')

app.whenReady().then(() => {
  ipcMain.handle('ping', () => 'pong')
  // ...
})

// renderer.js
const func = async () =>  await window.versions.ping()
func()

主进程到渲染器进程

首先需要指定哪一个渲染器接收数据。

数据通过 WebContents 实例发送。

此实例包含一个 send 方法,使用方法与 ipcRenderer.send 相同。

// main.js
const { app, BrowserWindow, Menu, ipcMain } = require('electron')
const path = require('path')

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      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')

  // Open the DevTools.
  mainWindow.webContents.openDevTools()
}

app.whenReady().then(() => {
  ipcMain.on('counter-value', (_event, value) => {
    console.log(value) // will print value to Node console
  })
  createWindow()

  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

// preload.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  handleCounter: (callback) => ipcRenderer.on('update-counter', callback)
})

// renderer.js
const counter = document.getElementById('counter')

window.electronAPI.handleCounter((event, value) => {
  const oldValue = Number(counter.innerText)
  const newValue = oldValue + value
  counter.innerText = newValue
  event.sender.send('counter-value', newValue)
})

渲染器进程到渲染器进程

没有直接方法。可选方法:

  • 将主进程作为中继器进行转发。
  • 从主进程将一个 MessagePort 传递到两个渲染器。将允许初始设置后渲染器之间直接进行通信。

对象序列化

Electron 的 IPC 实现使用 HTML 标准 结构化克隆算法 来序列化进程间传递对象。因此,只有某些类型对象可以通过 IPC 通道传递。

特别是 DOM 对象, Node 和 Electron 中由 C++ 类支持的对象无法进行结构化克隆序列化。


学习 Electron - 进程通信
http://localhost:8080/archives/fed1b5fb-f304-438b-b796-80dc48e620b8
作者
inksha
发布于
2024年09月14日
许可协议