Inter-Process Communication (IPC),进程间通信,主要负责 Electron 中渲染进程跟主进程之间的通信。

ipcMain 和 ipcRenderer

ipcMain:是一个位于主进程的事件发射器模块,用于接收和处理渲染进程发送的异步和同步消息。也可以向渲染进程发送消息。

ipcRenderer:是一个位于渲染进程的时间发射器模块,提供了可以向主进程发送异步和同步消息的方法。也可以接收来自主进程的消息。

在 Electron 中,进程间通信是通过在 ipcMain 和 ipcRenderer 模块中定义的 “channels” 通道。

渲染进程到主进程(一)

第一种从渲染进程到主进程的方式,是使用 ipcRenderer.send 发送消息和 ipcMain.on 接收消息。

以下是来自官网的一个例子:

  1. 在主进程使用 ipcMain.on 监听事件。

    // main.js
    function handleSetTitle(event, title) {
      const webContents = event.sender;
      const win = BrowserWindow.fromWebContents(webContents);
      win.setTitle(title);
    }
    
    app.whenReady().then(() => {
      ipcMain.on('set-title', handleSetTitle);
      createWindow();
    });
    
  2. 通过预加载暴露 ipcRenderer.send

    // preload.js
    const { contextBridge, ipcRenderer } = require('electron');
    contextBridge.exposeInMainWorld('electronAPI', {
      sendTitle: (title) => ipcRenderer.send('set-title', title),
    });
    

    默认情况下,渲染进程没有 Node.js 或 Electron 模块的访问权,需要通过 contextBridge.exposeInMainWorld 来选择暴露的 API。在预加载脚本中暴露一个全局的 window.electronAPI 供渲染进程调用。

  3. 渲染进程调用

    // renderer.js
    btn.addEventListener('click', () => {
      window.electronAPI.sendTitle('Hello World!');
    });
    

如果需要主进程回复消息,则需要使用 event.replay

渲染进程到主进程(二)

另一个更常见的方式是使用 ipcRenderer.invoke 发送消息和 ipcMain.handle 接收消息。

因为我们经常需要从渲染进程向主进程发送消息,并等待主进程的响应结果。

以下是来自官网的一个例子:

  1. 在主进程使用 ipcMain.handle 监听事件。

    // main.js
    
    async function handleFileOpen() {
      const { canceled, filePath } = await dialog.showOpenDialog({});
      if (!canceled) {
        return filePath[0];
      }
    }
    
    app.whenReady().then(() => {
      ipcMain.handle('dialog:openFile', handleFileOpen);
      createWindow();
    });
    
  2. 通过预加载暴露 ipcRenderer.invoke

    // preload.js
    const { contextBridge, ipcRenderer } = require('electron');
    contextBridge.exposeInMainWorld('electronAPI', {
      openFile: () => ipcRenderer.invoke('dialog:openFile'),
    });
    
  3. 渲染进程调用

    //renderer.js
    btn.addEventListener('click', async () => {
      const filePath = await window.electronAPI.openFile();
    });
    

如果需要主进程回复消息,则直接在 ipcMain.handle 里面 return 回去就可以了。而 ipcRenderer.invoke 会接收到一个异步的 Promise<pending> 的值。

What is the difference between IPC send / on and invoke / handle in electron? 里面的回答,invoke/handle 跟 send/on 并没有功能上的差别,而是一种更新更直观的 API 写法。

主进程到渲染进程

当你从主进程发送消息到渲染进程时,不再使用 ipcMain,而是通过 WebContents 实例,该实例包含了一个 send 方法。

以下是来自官网的一个例子:

  1. 使用 WebContents.send 发送消息。

    // main.js
    
    function createWindow() {
      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',
            },
          ],
        },
      ]);
    }
    
  2. 通过预加载暴露 ipcRenderer.on

    // preload.js
    const { contextBridge, ipcRenderer } = require('electron');
    
    contextBridge.exposeInMainWorld('electronAPI', {
      onUpdateCounter: (callback) => ipcRenderer.on('update-counter', callback),
    });
    

    当加载预加载脚本之后,渲染进程可以监听 window.electronAPI.onUpdateCounter 事件函数。

  3. 渲染进程调用

    window.electronAPI.onUpdateCounter((event, arg) => {
      //...
    });
    

渲染进程到渲染进程(一)

渲染进程到渲染进程可以通过主进程作为中间人进行消息通讯。

可以使用 Map 对象记录不同窗口的 webContents 对象。

const map = new Map();

const createWindow = () => {
  const mainWindow = new BrowserWindow({
    //...
  });
  const otherWindow = new BrowserWindow({
    //...
  });

  map.set('main', mainWindow.webContents);
  map.set('other', otherWindow.webContents);
};

渲染进程到渲染进程(二)

渲染进程到渲染进程可以通过 ipcRenderer.sendTo(webContentsId, channel, ...args) 来进行消息传递。

另一个渲染进程使用 ipcRenderer.on(channel, listener) 监听发来的消息。

参考链接