Electron 进程通信
Electron 进程通信
本文作者:IMWeb laynechen 原文出处:IMWeb社区 未经同意,禁止转载
Electron 中的进程分类
在 Electron
中,存在两种进程:主进程和渲染进程。
主进程 (Main Process)
一个 Electron 应用只有 一个主进程
当我们执行 electron .
命令后, Electron
会运行当前目录(.)下的 package.json
文件中 main
字段指定的文件。而运行该文件的进程既是主进程。
运行在主进程中的脚本可以通过创建一个窗口,并传入 URL
,让这个窗口加载一个网页来展示图形界面。
与创建 GUI 相关的接口只应该由主进程来调用。
渲染进程 (Renderer Process)
在Electron里的每个页面都有它自己的进程,叫作渲染进程。主进程通过实例化 BrowserWindow
,每个 BrowserWindow
实例都在它自己的渲染进程内返回一个 web 页面。当 BrowserWindow
实例销毁时,相应的渲染进程也会终止。
渲染进程由主进程进行管理。每个渲染进程都是相互独立的,它们只关心自己所运行的 web 页面。
问题
这篇文章主要要解决的问题是:
- Electron 与 View 层(网页),也就是主进程与渲染进程是如何进行通信的?
- 不同的通信是如何实现的?
先解决第一个问题。
Electron 与 View 层(网页)是如何进行通信的?
Electron 提供了两种通信方法:
1. 利用 ipcMain
和 ipcRenderer
模块
官方文档 上有使用这两个模块进行进程通信的例子:
// In main process. const {ipcMain} = require('electron') ipcMain.on('asynchronous-message', (event, arg) => { console.log(arg) // prints "ping" event.sender.send('asynchronous-reply', 'pong') }) ipcMain.on('synchronous-message', (event, arg) => { console.log(arg) // prints "ping" event.returnValue = 'pong' })
// In renderer process (web page). const {ipcRenderer} = require('electron') console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong" ipcRenderer.on('asynchronous-reply', (event, arg) => { console.log(arg) // prints "pong" }) ipcRenderer.send('asynchronous-message', 'ping')
渲染进程可以通过 ipcRenderer
模块的 send
方法向主进程发送消息。在主进程中,通过 ipcMain
模块设置监听 asynchronous-message
和 synchronous-message
两个事件,当渲染进程发送时就可以针对不同的事件进行处理。
主进程监听事件的回调函数中,会传递 event
对象及 arg
对象。arg
对象中保存渲染进程传递过来的参数。通过 event.sender
对象,主进程可以向渲染进程发送消息。如果主进程执行的是同步方法,还可以通过设置 event.returnValue 来返回信息。
上面说了渲染进程如何向主进程发送消息,但主进程也可以主动向渲染进程发送消息
在主进程中,我们会创建一个 BrowserWindow
对象,这个对象有 webContents
属性。webContets
提供了 send
方法来实现向渲染进程发送消息。当然 webContents
对象远不止这两个通信方法,具体可以看 webContents
下面是官方文档提供的使用 webContents
实现通信的例子:
// In the main process. const {app, BrowserWindow} = require('electron') let win = null app.on('ready', () => { win = new BrowserWindow({width: 800, height: 600}) win.loadURL(`file://${__dirname}/index.html`) win.webContents.on('did-finish-load', () => { win.webContents.send('ping', 'whoooooooh!') }) })
<!-- index.html --> <html> <body> <script> require('electron').ipcRenderer.on('ping', (event, message) => { console.log(message) // Prints 'whoooooooh!' }) </script> </body> </html>
注意,webContents.on
监听的是已经定义好的事件,如上面的 did-finish-load
。要监听自定义的事件还是通过 ipcMain
和 ipcRenderer
。
渲染进程的监听事件回调函数中,也可以通过 event.sender
来向主进程发送消息。这个对象只是 ipcRenderer
的引用(event.sender === ipcRenderer)。因此,event.sender
发送的消息在主进程中还是需要通过 ipcMain.on
方法来监听,而不是通过 webContents.on
方法。
2. 利用 electron.remote 模块
在渲染进程中,可以通过
const { remote } = require('electron');
获取到 remote
对象,通过 remote
对象可以让渲染进程访问/使用主进程的模块。例如,通过 remote
在渲染进程中新建一个窗口:
const {BrowserWindow} = require('electron').remote let win = new BrowserWindow({width: 800, height: 600}) win.loadURL('https://github.com')
同样的,我们也可以通过 remote
对象访问到 app
对象。这样我们就可以访问到我们在主进程中挂载到 electron.app
对象上的方法。
如:
main.js
文件:
// In main process const { app } = require('electron'); const utils = require('./utils'); app.utils = utils; // 将在 Electron 层实现的接口绑定到 app 上
index.js
文件(被网页引用的脚本文件):
const { remote } = require('electron'); // In renderer process function() { // remote.app.utils 对象与上述文件中的 utils 对象是一样的。 remote.app.utils.test(); }
Electron 的两种进程通信方法是如何实现的?
知道怎么用还不够,还需要了解 Electron 是如何实现这两种通信方法的,以及 Electron 为什么要实现两种通信方法,这两种通信方法的有什么不同的地方。弄清楚这些开发起来才会对程序的数据流比较清晰。
ipcMain
和 ipcRenderer
The ipcMain module is an instance of the EventEmitter class. When used in the main process, it handles asynchronous and synchronous messages sent from a renderer process (web page). Messages sent from a renderer will be emitted to this module.
ipcMain
和 ipcRenderer
都是 EventEmitter
类的一个实例。而 EventEmitter
类由 NodeJS 中的 events 模块导出。
events.EventEmitter
EventEmitter
类是 NodeJS 事件的基础,实现了事件模型需要的接口, 包括 addListener
,removeListener
, emit
及其它工具方法. 同原生 JavaScript
事件类似, 采用了发布/订阅(观察者)的方式, 使用内部 _events
列表来记录注册的事件处理器。
我们通过 ipcMain
和ipcRenderer
的 on
、send
进行监听和发送消息都是 EventEmitter
定义的相关接口。
那么 ipcMain 和 ipcRenderer 是如何实现这些接口的呢?
ipc-renderer.js
const binding = process.atomBinding('ipc') ... // Created by init.js. const ipcRenderer = v8Util.getHiddenValue(global, 'ipc') ipcRenderer.send = function (...args) { return binding.send('ipc-message', args) } .... module.exports = ipcRenderer
调用了 atomBinding('ipc')
得到的 binding
对象的 send
方法。能力有限,就分析到这。后面 binding.send
应该就是 IPC 相关的实现了:对传送的数据进行序列化和反序列化。
// 主进程 ipcMain.on('test1', (e) => { const obj = {}; obj.toJSON = () => 'call toJSON'; e.returnValue = obj; }) ipcMain.on('test2', (e) => { const obj = { name: '123' }; e.returnValue = obj; })
// 渲染进程 let returnValue = ipcRenderer.sendSync('test1'); console.log(typeof returnValue, returnValue); // 'string call toJSON' returnValue = ipcRenderer.sendSync('test2'); console.log(typeof returnValue, returnValue); // 'object Object name: "123"__proto__: Object'
从渲染进程输出的消息可以看到,主进程将返回值调用 toJSON
后传递给渲染进程。渲染进程再对传输过来的内容进行反序列化。
remote
远程对象
通过 remote
对象,我们可以不必发送进程间消息来进行通信。但实际上,我们在调用远程对象的方法、函数或者通过远程构造函数创建一个新的对象,实际上都是在发送一个同步的进程间消息(官方文档 上说这类似于 JAVA 中的 RMI)。
也就是说,remote
方法只是不用让我们显式的写发送进程间的消息的方法而已。在上面通过 remote
模块创建 BrowserWindow
的例子里。我们在渲染进程中创建的 BrowserWindow
对象其实并不在我们的渲染进程中,它只是让主进程创建了一个 BrowserWindow
对象,并返回了这个相对应的远程对象给了渲染进程。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?