通过前面的学习,我们知道在Electron
中,主进程和渲染进程是被换分成不同的功能的,主进程有完整的Node
环境,而渲染进程默认是运行的是浏览器环境。这样划分的好处是:
- 安全性:渲染进程运行在沙盒环境中,限制了对底层操作系统的访问和敏感资源的操作。直接在渲染进程中开启 Node.js 环境可能会带来潜在的安全风险,因为 Node.js 具有强大的功能和系统级访问权限,可能会被恶意利用或不当使用。
- 性能:渲染进程主要负责显示用户界面,处理用户交互和渲染页面。将 Node.js 环境直接放在渲染进程中可能会对性能产生负面影响,因为 Node.js 的运行环境相对较重,可能会消耗大量的内存和 CPU 资源,从而影响渲染进程的响应性能和用户体验。
- 分离关注点:主进程和渲染进程在 Electron 中有不同的职责和关注点。主进程负责管理应用程序的生命周期、系统级功能和与底层操作系统的交互,而渲染进程负责处理用户界面和与用户的交互。通过进程间通信,可以保持这种分离,使代码更易于维护、调试和扩展。
但是我们知道,开发的时候,我们很可能需要在渲染进程中进行一些只能在主进程中运行的操作。这就需要进程间通信了,在渲染进程中需要的时候,可以给主进程发信息,让主进程帮忙处理。
我们之前说过一种直接给渲染进程开启node
环境,使用remote
模块的方式,并不十分推荐。
预加载脚本(preload
)
Electron
不推荐在渲染进程开启Nodejs
环境,那也就意味着我们无法在渲染进程中使用NodeJS
API,但有时候我们又真的很需要使用NodeJS
API。“Preload 脚本”就是用来解决这个问题的。
用大白话来说就是:我们可以在主进程创建窗口的时候,指定一些脚本(内容是我们定的),这些脚本将来是在渲染进程中使用的,但是先于网页内容加载,由于是在主进程的时候就预加载了,所以能使用NodeJS
API。
从 Electron 20 开始,预加载脚本默认 沙盒化 ,不再拥有完整 Node.js 环境的访问权,实际上,这意味着我们只拥有一个 require
函数,这个函数只能访问一组有限的 API。
可用的 API | 详细信息 |
---|---|
Electron 模块 | 仅限渲染进程模块 |
Node.js 模块 | events 、timers 、url |
Polyfilled 的全局模块 | Buffer 、process 、clearImmediate 、setImmediate |
目前我使用的electron
的版本是 v25
。下面写个示例体验一下:
创建一个预加载脚本
p1.js
,我们可以在里面编写我们的代码,此脚本中可以使用Nodejs
API。// p1.js const { contextBridge } = require('electron') // 将xxxx对象暴露给渲染进程中的全局对象,以便在渲染进程中直接访问 contextBridge.exposeInMainWorld('xxxx', { name: '如花', run: function () { return '小子你跑的真快' } })
contextBridge.exposeInMainWorld(apiKey, apiObject)
此方法将指定的
apiObject
对象暴露给渲染进程中的全局对象,以便在渲染进程中直接访问。apiKey
是一个字符串,用于在全局对象中创建一个属性,该属性将指向apiObject
。创建窗口的时候,指定
preload
配置// main.js const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { // 配置预加载脚本(这里需要是个绝对路径) preload: path.join(__dirname, 'p1.js') }, }) win.loadFile('index.html')
在渲染进程中使用预加载脚本暴露的方法
<!--index.html--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>您好,世界</title> </head> <body> <h1>您好,世界!</h1> <script> // 调用预处理脚本中暴露的数据 console.log(xxxx.name); console.log(xxxx.run()); </script> </body> </html>
参考文档:https://www.electronjs.org/zh/docs/latest/tutorial/tutorial-preload
进程间通信(IPC
)
在 Electron 中,使用 ipcMain
和 ipcRenderer
模块来处理进程间通信。
- 在主进程中,可以使用
ipcMain
模块监听事件,通过ipcMain.on()
方法注册事件处理程序,接收渲染进程发送的消息,并通过event.sender.send()
方法向渲染进程发送回复。 - 在渲染进程中,可以使用
ipcRenderer
模块发送消息,通过ipcRenderer.send()
方法发送消息给主进程,并使用ipcRenderer.on()
方法监听主进程发送的消息。
由于渲染进程中默认无法使用NodeJS
API,也就无法使用require
导入模块,所以我们需要将ipcRenderer
模块的相关内容在预处理脚本中暴露,才能在渲染进程中使用。
渲染进程向主进程通信(单向)
// main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path');
app.on('ready', () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'p1.js')
},
});
// 监听 fromSon 事件(频道)
ipcMain.on('fromSon', function (event, arg1) {
console.log(arg1);
// 在主进程中设置窗口的标题
win.setTitle(arg1)
})
win.loadFile('index.html')
})
// p1.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('elecAPI', {
// 定义sendToFather方法,该方法可以在渲染进程中使用
sendToFather: function (val) {
// 使用ipcRenderer.send()方法向主进程指定频道发送信息
ipcRenderer.send('fromSon', val)
}
})
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>您好,世界</title>
</head>
<body>
<h1>您好,世界!</h1>
<button id="btn">发送</button>
<script>
let btn = document.getElementById('btn');
btn.onclick = function () {
// 调用预处理脚本中定义的方法,向主进程发送数据
elecAPI.sendToFather('来自渲染进程的问候')
}
</script>
</body>
</html>
上面的代码中,我们在主进程中使用 ipcMain.on()
方法监听 fromSon
频道(事件)。在渲染进程中使用 ipcRenderer.send()
方法向fromSon
频道发送数据。
渲染进程和主进程双向通信
这可以通过 ipcRenderer.invoke
与 ipcMain.handle
搭配使用来完成双向通信。
ipcRenderer.invoke()
方法允许渲染进程向主进程发送请求,并等待主进程返回结果。ipcMain.handle()
方法可以为指定频道注册处理函数,这个处理函数可以接收请求的参数并执行相应的操作,然后返回一个结果给渲染进程。
// main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path');
app.on('ready', () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'p1.js')
},
});
// 在主进程中监听 fromSon 事件,并为它绑定个处理函数。
// 在处理函数中return的值就是返回给渲染进程的数据。
ipcMain.handle('fromSon', function (event, arg1) {
console.log(arg1);
return '您的问候已收到,这是我的回复'
})
win.loadFile('index.html')
})
// p1.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('elecAPI', {
// 定义sendToFather方法,该方法可以在渲染进程中使用
sendToFather: function (val) {
// 使用ipcRenderer.invoke()方法向主进程指定频道发送信息,它会返回一个Promise
return ipcRenderer.invoke('fromSon', val)
}
})
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>您好,世界</title>
</head>
<body>
<h1>您好,世界!</h1>
<button id="btn">发送</button>
<script>
let btn = document.getElementById('btn');
btn.onclick = async function () {
// 调用预处理脚本中定义的方法,向主进程发送数据,并接收返回值
let res = await elecAPI.sendToFather('来自渲染进程的问候')
console.log(res);
}
</script>
</body>
</html>
主进程向渲染进程通信(单向)
将消息从主进程发送到渲染进程时,需要指定是哪一个渲染进程接收消息。 消息需要通过该渲染进程的 WebContents
实例发送到渲染进程。 此 WebContents 实例包含一个 send
方法,其使用方式与 ipcRenderer.send
相同。
// main.js
const { app, BrowserWindow } = require('electron')
const path = require('path');
app.on('ready', () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'p1.js')
},
});
win.loadFile('index.html')
// 启动5秒后,向渲染进程发送数据
setTimeout(function () {
// 获取渲染进程的 WebContents,用使用它的send方法向渲染进程发送数据
win.webContents.send('toSon', '来自主进程的问候')
}, 5000)
})
// p1.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('elecAPI', {
// 定义 fromFather方法,该方法可以在渲染进程中使用
fromFather: function (callback) {
// 使用ipcRenderer.on() 方法接收指定频道传来的数据,并用我们传入的处理函数处理它
ipcRenderer.on('toSon', callback)
}
})
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>您好,世界</title>
</head>
<body>
<h1>您好,世界!</h1>
<script>
// 使用预处理脚本中定义的函数,间接监听从主进程中传开的数据
elecAPI.fromFather(function (event, arg1) {
console.log(arg1);
})
</script>
</body>
</html>
渲染进程之间的通信
目前没有直接的方法可以进行渲染进程之间的通信,不过可以将主进程作为渲染进程之间的消息代理。 这需要将消息从一个渲染进程发送到主进程,然后主进程将消息转发到另一个渲染进程。或者使用第三方存储方案(如:localStorage、数据库等)进行中转
参考文档:
https://www.electronjs.org/zh/docs/latest/tutorial/ipc