electron进程通信

181 阅读5分钟

electron进程通信

存在原因

electron主进程与渲染进程相互独立且不可替换,因此无法直接访问node.js接口或直接访问dom,解决的方式是进程间通信,通信方式:共享内存

多进程模型

概念:

  1. 进程是CPU资源分配的基本单位,线程是独立运行和独立调度的基本单位(CPU上真正运行的是线程)。
  2. 进程拥有自己的资源空间,一个进程包含若干个线程,线程与CPU资源分配无关,多个线程共享同一进程内的资源。
  3. 线程的调度与切换比进程快很多。

electron继承了Chromium 的多进程架构,这使得此框架在架构上非常相似于一个现代的网页浏览器。

多进程好处

  • 每开一个标签页相当于新起一个进程,进程与进程之间是任务与任务之间的关系,进程之间相互隔离,这样不会导致一个网站的崩溃影响到整个浏览器,同时也可防止恶意代码的注入
  • 拓展性更强,新的第三方插件或者网页相当于一个新的任务,cpu分配资源更加灵活

electron进程

Chrome的多进程架构

主进程

主进程在node.js环境中运行,因此具有require模块和使用所有node.js API的能力。

主进程的主要目的是使用 BrowserWindow 模块创建和管理应用程序窗口。

BrowserWindow 类的每个实例创建一个应用程序窗口,且在单独的渲染器进程中加载一个网页。 您可从主进程用 window 的 webContent 对象与网页内容进行交互。

渲染进程

每个 Electron 应用都会为每个打开的 BrowserWindow ( 与每个网页嵌入 ) 生成一个单独的渲染器进程。 洽如其名,渲染器负责 渲染 网页内容。 所以实际上,运行于渲染器进程中的代码是须遵照网页标准的 (至少就目前使用的 Chromium 而言是如此) 。

electron进程间通信

IPC通道

在 Electron 中,进程使用 ipcMainipcRenderer 模块,通过开发人员定义的“通道”传递消息来进行通信。 这些通道是 任意 (您可以随意命名它们)和 双向 (您可以在两个模块中使用相同的通道名称)的。

分别对应主进程和渲染进程

上下文隔离

上下文隔离功能将确保您的 预加载脚本 和 Electron的内部逻辑 运行在所加载的 webcontent网页 之外的另一个独立的上下文环境里。

安全

 // ❌ 错误使用
 contextBridge.exposeInMainWorld('myAPI', {
   send: ipcRenderer.send
 })

它直接暴露了一个没有任何参数过滤的高等级权限 API 。 这将允许任何网站发送任意的 IPC 消息,这不会是你希望发生的。 相反,暴露进程间通信相关 API 的正确方法是为每一种通信消息提供一种实现方法。

 // ✅ 正确使用
 contextBridge.exposeInMainWorld('myAPI', {
   loadPreferences: () => ipcRenderer.invoke('load-prefs')
 })

进程通信模式

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

  1. main.js中设置监听器,核心API

     ipcMain.on()
     //e.g
     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. 通过预加载脚本preload.js暴露,核心API

     ipcRenderer.send()
     //
     const { contextBridge, ipcRenderer } = require('electron')
     ​
     contextBridge.exposeInMainWorld('electronAPI', {
         setTitle: (title) => ipcRenderer.send('set-title', title)
     })
    
  3. renderer.js中调用

过程: 主进程->预加载脚本->渲染进程->todo

渲染器进程到主进程(双向)(官方建议方式)

双向 IPC 的一个常见应用是从渲染器进程代码调用主进程模块并等待结果。 这可以通过将 ipcRenderer.invokeipcMain.handle 搭配使用来完成。

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

    说明: 主进程创建函数,调用node环境下API,每当渲染器进程通过 dialog:openFile 通道发送 ipcRender.invoke 消息时,此函数被用作一个回调。 然后,返回值将作为一个 Promise 返回到最初的 invoke 调用。

  2. 预加载脚本preload.js,暴露函数

  3. 渲染进程rederer.js中调用

过程: 主进程->预加载脚本->渲染进程->dom->主进程->todo

主进程到渲染器进程

将消息从主进程发送到渲染器进程时,需要指定是哪一个渲染器接收消息。 消息需要通过其 WebContents 实例发送到渲染器进程。 此 WebContents实例包含一个 send 方法,其使用方式与 ipcRenderer.send 相同。

  1. 使用 webContents 模块发送消息

     //该实例创建了一个自定义的menu菜单
     const {app, BrowserWindow, Menu, ipcMain} = require('electron')
      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)
     //此示例将在menu中创建一个选项卡,控制dom界面数字的增减
     //选项卡点击后发送消息到指定的渲染器进程
     ​
    
  2. 在预加载器preload.js中暴露

     const { ipcRenderer } = require('electron')
     ​
     window.addEventListener('DOMContentLoaded', () => {
         const counter = document.getElementById('counter')
         ipcRenderer.on('update-counter', (_event, value) => {
             const oldValue = Number(counter.innerText)
             const newValue = oldValue + value
             counter.innerText = newValue
         })
     })
    
  3. 渲染器(renderer.js)调用

渲染器进程到渲染器进程

没有直接的方法可以使用 ipcMainipcRenderer 模块在 Electron 中的渲染器进程之间发送消息。 为此,您有两种选择:

  • 将主进程作为渲染器之间的消息代理。 这需要将消息从一个渲染器发送到主进程,然后主进程将消息转发到另一个渲染器。
  • 从主进程将一个 MessagePort 传递到两个渲染器。 这将允许在初始设置后渲染器之间直接进行通信。