Electron23 进程间通讯(ipcRenderer、ipcMain)与数据共享

进程间通讯

渲染进程=>主进程=>渲染进程

异步

在渲染器进程 (网页) 中

1
2
3
4
5
6
const { ipcRenderer } = require("electron");

ipcRenderer.send('downloadfile', 'http://www.psvmc.cn/favicon.ico')
ipcRenderer.on('downloadfile-result', (event, arg) => {
console.log(arg)
})

在主进程中

1
2
3
4
5
6
const { ipcMain } = require("electron")

ipcMain.on("downloadfile", (event, arg) => {
console.log(arg); // prints "ping"
event.reply("downloadfile-result", "E://1.jpg");
});

回复可以用

1
event.sender.send('downloadfile-result', '主进程已收到消息');

注意

event.reply可以看作event.sender.send的便捷用法。

同步

在渲染器进程 (网页) 中

1
2
const { ipcRenderer } = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"

在主进程中

1
2
3
4
5
const { ipcMain } = require('electron')
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.returnValue = 'pong'
})

主进程=>渲染进程

在主进程中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const { ipcMain } = require('electron')

let rtmpWindow = new BrowserWindow({
width: 400,
height: 600,
frame: false,
hasShadow: false,
show: false,
title: "",
webPreferences: {
nodeIntegration: true
}
});
rtmpWindow.loadFile("views/rtmp.html");
rtmpWindow.webContents.openDevTools()


rtmpWindow.webContents.on('did-finish-load', () => {
//下面的这行代码要在上面的BrowserWindow加载完成之后调用
rtmpWindow.webContents.send('asynchronous-msg',"test");
})

在渲染器进程 (网页) 中

1
2
3
4
5
const { ipcRenderer } = require('electron')

ipcRenderer.on('asynchronous-msg', (event, arg) => {
console.log(arg) // prints "pong"
})

渲染进程=>渲染进程

使用全局共享属性

使用全局共享属性或者用 Storage API( localStoragesessionStorage 或者 IndexedDB)。

但不具备事件机制,没有实质的通信功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 主进程中在global上自定义对象
global.saveDefault= {
token: 'default value',
name: 'default value',
password: 'default value',
}

// 在登录页 In page 1
require('electron').remote.getGlobal('saveDefault').token= 'token'
require('electron').remote.getGlobal('saveDefault').name= 'name'


// 在主页 In page 2 就可以获取到
console.log(require('electron').remote.getGlobal('saveDefault').name)
console.log(require('electron').remote.getGlobal('saveDefault').token)
console.log(require('electron').remote.getGlobal('saveDefault').password)

利用主进程做消息中转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 渲染进程1
ipcRenderer.send('ping-event', 'ping');

// 在主进程中
ipcMain.on(
'ping-event',
(event, arg) => {
yourWindow.webContents.send('pong-event', 'something');
}
);

// 渲染进程2
ipcRenderer.on(
'pong-event',
(event, arg) => {
// do something
}
);

使用 ipcRenderer.sendTo()

1
2
3
// 渲染进程
ipcRenderer.sendTo(webContentsId, channel, [, arg1][, arg2][, ...]);
ipcRenderer.sendTo(winId, 'ping', 'someThing');

页面数据共享

渲染进程之间

在两个网页(渲染进程)间共享数据最简单的方法是使用浏览器中已经实现的 HTML5 API。

其中比较好的方案是用 Storage API( localStoragesessionStorage 或者 IndexedDB)。

所有进程间

但是如果要想在主进程和渲染进程之间共享数据,就不能用上面所说的方式了。

可以用 IPC 机制global.sharedObject

IPC

Electron 内的 IPC 机制实现。

global.sharedObject

将数据存在主进程的某个全局变量中,然后在多个渲染进程中使用 remote 模块来访问它。

在主进程中

1
2
3
global.sharedObject = {
username: 'default value'
}

在第一个页面中

1
2
const remote = window.require("electron").remote;
remote.getGlobal('sharedObject').username = '000';

在第二个页面中

1
2
const remote = window.require("electron").remote;
console.log(remote.getGlobal('sharedObject').username);

主进程

1
2
3
global.sharedObject.username = "123";

console.info(global.sharedObject.username);

invoke和send

ipcRenderer.invokeipcRenderer.send 是 Electron 中渲染进程(通常是网页)与主进程进行通信时使用的两个方法。

区别

它们存在以下区别:

通信模式

  • ipcRenderer.send:采用的是单向通信模式。当在渲染进程中调用 ipcRenderer.send 方法时,它会向主进程发送一个异步消息。

    渲染进程在发送消息后不会等待主进程的响应,而是会继续执行后续的代码,也就是说渲染进程不会主动获取主进程针对该消息的处理结果。

  • ipcRenderer.invoke:属于双向通信模式。它会向主进程发送一个消息,并且会暂停当前代码的执行,等待主进程返回响应结果。

    只有当主进程处理完消息并返回结果后,渲染进程才会继续执行后续代码,所以可以直接获取主进程的处理结果。

返回值

  • ipcRenderer.send:该方法没有返回值。因为它只是单纯地发送消息,不关心主进程的响应情况,所以不会返回任何数据给调用者。
  • ipcRenderer.invoke:会返回一个 Promise 对象。这个 Promise 对象在主进程成功处理消息并返回结果时会被 resolve,如果在通信过程中出现错误,Promise 则会被 reject。因此,可以使用 async/await 或者 .then().catch() 方法来处理主进程返回的结果或错误。

主进程处理方式

  • ipcRenderer.send:主进程需要使用 ipcMain.on 来监听渲染进程发送的消息。

    在事件处理函数中,主进程可以对消息进行处理,但如果需要向渲染进程发送响应,还需要额外调用 event.sender.send 方法。

  • ipcRenderer.invoke:主进程需要使用 ipcMain.handle 来处理渲染进程的消息。

    ipcMain.handle 的处理函数可以返回一个值或者 Promise,返回的值会作为响应发送给渲染进程。

代码示例

ipcRenderer.send

渲染进程代码

1
2
3
const { ipcRenderer } = require('electron');
// 发送消息到主进程
ipcRenderer.send('message-to-main', '这是一条来自渲染进程的消息');

主进程代码

1
2
3
4
5
6
const { ipcMain } = require('electron');
ipcMain.on('message-to-main', (event, arg) => {
console.log(arg); // 输出: 这是一条来自渲染进程的消息
// 如果需要回复渲染进程
event.sender.send('reply-to-renderer', '主进程已收到消息');
});

ipcRenderer.invoke

渲染进程代码

1
2
3
4
5
6
7
8
9
10
const { ipcRenderer } = require('electron');
async function getMainProcessResponse() {
try {
const response = await ipcRenderer.invoke('request-to-main', '请求数据');
console.log(response);
} catch (error) {
console.error(error);
}
}
getMainProcessResponse();

主进程代码

1
2
3
4
5
const { ipcMain } = require('electron');
ipcMain.handle('request-to-main', (event, arg) => {
console.log(arg); // 输出: 请求数据
return '这是主进程的响应';
});

使用场景

  • ipcRenderer.send:适用于那些不需要从主进程获取反馈的场景,例如通知主进程执行某个操作(如打开一个新窗口、记录日志等)。
  • ipcRenderer.invoke:适用于需要从主进程获取数据或结果的场景,比如请求主进程读取文件内容、进行数据库查询等。