Electron桌面应用中有两个进程,分别是Main
主进程和Renderer
渲染进程。两个进程间有多种方法进行通信。
一、主进程和渲染进程
1、主进程Main
main.js在启动应用后就创建了一个主进程main process,它可以通过electron中的一些模块直接与原生GUI(在你的应用窗口)交互。
2、渲染进程Renderer
仅启动主进程并不能给你的应用创建应用窗口。窗口是通过main文件里的主进程调用叫BrowserWindow
的模块创建的。每个页面都是运行在自己的进程里,这些进程我们称之为渲染进程。渲染进程会在窗口中渲染出web页面(引用了CSS,JavaScript,图片等的HTML文件)。web页面是Chromium渲染的,因为各系统下标准是统一的的,所以兼容性很好。
3、主进程于渲染进程的关系
主进程通过构造BrowserWindow
实例来创建页面。每个 BrowserWindow
实例都在自己的渲染进程里运行页面。当一个 BrowserWindow
实例被销毁后,相应的渲染进程也会被终止。
主进程管理所有页面和与之对应的渲染进程。每个渲染进程都是相互隔离的,并且只知道运行在该进程里的页面。
在页面里调用本地GUI是不允许的,因为在Web页面里管理本地GUI资源是非常危险而且容易造成资源泄露。如果你想在网页里进行GUI操作,该页面的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI 操作。
二、几种通信方式
1、ipc
模块
ipcMain
模块ipcRenderer
模块ipcMain
模块和ipcRendere
r是类EventEmitter
的实例。
在Electron项目中,使用require
来引入这个模块,而在Electron-vue中,由于使用了webpack,可以使用import
引入。
1.1、ipcMain
ipcMain.on(channel, listener)
监听 channel,当接收到新的消息时 listener 会以 listener(event, args...) 的形式被调用。
ipcMain.once(channel, listener)
添加一次性的 listener。当且仅当下一个消息发送到 channel 时 listener 才会被调用,随后listener会被移除。
ipcMain.removeListener(channel, listener)
从监听器数组中移除监听 channel 的指定 listener。
ipcMain.removeAllListeners([channel])
删除所有监听者,或特指的 channel 的所有监听者。
1.2、ipcRenderer
ipcRenderer.on(channel, listener)
监听 channel, 当新消息到达,将通过 listener(event, args...) 调用 listener
ipcRenderer.once(channel, listener)
ipcRenderer.removeListener(channel, listener)
ipcRenderer.removeAllListeners(channel)
ipcRenderer.send(channel[, arg1][, arg2][, ...])
通过 channel
发送异步消息到主进程,可以携带任意参数。 在内部,参数会被序列化为 JSON,因此参数对象上的函数和原型链不会被发送。
主进程可以使用 ipcMain
监听channel来接收这些消息。
-
ipcRenderer.sendSync(channel[, arg1][, arg2][, ...])
通过 channel
发送同步消息到主进程,可以携带任意参数。 在内部,参数会被序列化为 JSON,因此参数对象上的函数和原型链不会被发送。
主进程可以使用 ipcMain
监听channel来接收这些消息,并通过 event.returnValue
设置回复消息。
-
ipcRenderer.sendTo(webContentsId, channel, [, arg1][, arg2][, ...])
通过channel
向具有WebContentSid
的窗口发送消息
-
ipcRenderer.sendToHost(channel[, arg1][, arg2][, ...])
就像 ipcRenderer.send
,不同的是消息会被发送到 host 页面上的 <webview>
元素,而不是主进程。
1.3、Renderer
进程向Main
进程发送消息,Main
进程进行回复(ipc模块没有Main进程向Renderer模块发送的方法)
- 发送消息时,事件名称为
channel
(如例子中的asynchronous-message)
1.3.1、异步消息
- 主进程将异步消息发送回发件人,需要使用event.sender.send(...)
// Renderer进程(为Vue页面或main.js页面)
import {ipcRenderer} from 'electron';
import Vue from 'vue';
<template>
<div>{{this.msg}}</div>
</template>
<script>
ipcRender.on('asynchronous-reply', function (event, arg) { // 接收到Main进程返回的消息
const message = `异步消息回复: ${arg}`
})
export default{
mounted(){
ipcRenderer.send('asynchronous-message', 'ping') //给主进程发送消息“ping”
}
}
//Main进程(为js文件)
import {ipcMain} from 'electron';
ipcMain.on('asynchronous-message',(event, arg) => { // arg为接受到的消息
event.sender.send('asynchronous-reply', 'pong'); // 返回一个'pong'
})
1.3.2、同步消息
可以使用ipc
模块在进程之间发送同步消息. 但请注意, 此方法的同步特性意味着它在完成任务时会阻止其他操作
- 主进程回复同步信息时,需要设置event.returnValue
// Renderer进程
import {ipcRenderer} from 'electron';
import Vue from 'vue';
<template>
<div>{{this.msg}}</div>
</template>
<script>
ipcRender.on('synchronous-reply', function (event, arg) { // 接收到Main进程返回的消息
const message = `异步消息回复: ${arg}`
})
export default{
mounted(){
ipcRenderer.sendSync('synchronous-message', 'ping') //给主进程发送消息“ping”
}
}
//Main进程
mport {ipcMain} from 'electron';
ipcMain.on('synchronous-message',(event, arg) => { // arg为接受到的消息
event.returnValue = 'pong' // 返回一个'pong'
})
2、webContents.send方法(Main进程主动向Renderer进程发送消息)
webContents.send
是BrowserWindow类
的一个方法,BrowserWindow类
用于创建一个程序窗口,实例化之后,设置窗口宽高,并设置其loadURL
(加载的页面),一个窗口就创建成功并开始显示。通信方法如下
mainWindow.webContents.send('list', res.data);
// mainWindow是一个BrowserWindow实例
在渲染进程中,依旧是使用ipcRenderer
对消息进行接收
ipcRenderer.on('list', (e, msg) => {
console.log(msg);
});
}
3、remote模块
remote模块支持RPC风格的通信,在渲染进程中获取主进程创建的一些全局对象和应用信息,还可以调用主进程所提供的一些方法,如重启应用、操作渲染进程等。
在Electron中, GUI 相关的模块 (如 dialog
、menu
等) 仅在主进程中可用, 在渲染进程中不可用。 为了在渲染进程中使用它们, 必须使用 remote
模块,。你可以调用 main 进程对象的方法, 而不必显式发送进程间消息, 类似于 Java 的 RMI 。
- 从渲染进程创建浏览器窗口的一个例子
const { BrowserWindow } = require('electron').remote;
let win = new BrowserWindow({ width: 800, height: 600 });
win.loadURL('https://github.com');
注意: 反向操作( 从主进程访问渲染进程) ,可以使用webContents.executeJavascript
3.1远程对象
remote 模块返回的每个对象 (包括函数) 表示主进程中的一个对象 (我们称它为远程对象或远程函数)。 当调用远程对象的方法时, 调用远程函数, 或者使用远程构造函数 (函数) 创建新对象时, 实际上是在发送同步进程消息。
在上面的示例中, BrowserWindow
和 win
(browser-window. md) 都是远程对象, new BrowserWindow 在渲染过程中没有创建 BrowserWindow 对象。 取而代之的是,它在主进程中创建了一个 BrowserWindow对象,并且在渲染进程中返回相应的远程对象,即win对象。
请注意只有可枚举属性才能通过 remote 进行访问。
3.2远程对象的生命周期
Electron 确保在渲染进程中的远程对象存在(换句话说,没有被垃圾收集),那主进程中的对应对象也不会被释放。当远程对象被垃圾收集之后,主进程中的对应对象才会被取消关联。如果远程对象在渲染进程泄露了(即,存在某个表中但永远不会释放),那么主进程中的对应对象也一样会泄露,所以你必须小心不要泄露了远程对象。
但是,字符串和数字等主要值的类型是通过复制发送的。
3.3给主进程传递回调函数
在主进程中的代码可以从渲染进程——remote模块——中接受回调函数,但是使用这个功能的时候必须非常非常小心。
首先,为了避免死锁,传递给主进程的回调函数会进行异步调用。所以不能期望主进程来获得传递过去的回调函数的返回值。
其次,传递给主进程的回调将持续到主进程垃圾回收。主进程会一直保持对这个回调函数的引用,除非明确的卸载它。如果不卸载,每次重新载入窗口都会再次绑定,这样每次重启就会泄露一个回调函数。为了避免这个问题,请确保清除对传递给主进程的渲染器回调的引用。 这涉及到清理事件处理程序, 或者确保主进程被明确告知取消引用来自正在退出的渲染程序的回调。
3.4访问主进程中的内置模块
主过程中的内置模块被添加为 remote 模块中的获取器,因此可以像 electron 模块一样直接使用它们
const app = require('electron').remote.app
console.log(app)
3.5remote的几个方法及属性
remote.require(module)
返回主进程中require(module) 返回的对象。 由其相对路径指定的模块将相对于主进程的入口点来解析。
remote.getCurrentWindow()
返回 BrowserWindow 即此网页所属的窗口
remote.getCurrentWebContents()
返回 WebContents 即此网页的 web 内容
remote.getGlobal(name)
返回主进程中 name (例如 global[name]) 的全局变量。
remote.process
主进程中的 process 对象。这与 remote.getGlobal('process') 相同, 但已被缓存。