两种进程


多进程架构

由于 Electron 使用 Chromium 来展示页面,所以 Chromium 的多进程结构也被充分利用,因此Electron也有两种进程:『主进程』和『渲染进程』。

Chromium浏览器的进程模型的优点:

  • 主进程和页面的渲染进程是分开的,保证了页面的奔溃不会导致主进程的奔溃;

  • 每个页面的渲染进程是独立的进程,保证了页面之间相互不影响。

 两种进程  - 图1

Electron对主进程和渲染进程都分别提供了不同的API,详情见可点击这里

主进程

主进程,在Electron里,运行package.json里的main脚本的进程被称为,通常是一个命名为main.js的文件,该文件是每个 Electron 应用的入口,它控制了应用的生命周期(从打开到关闭)。它能调用原生元素和创建新的渲染进程,而且整合了Node API 。

  • 产生渲染进程:主进程里的脚本能够通过创建Web页面渲染进程来显示GUI。
  • 调用原生API:打开 diaglog 和其它操作系统交互的操作API。
  • 启动与关闭程序:能够启动和退出整个应用程序。
     两种进程  - 图2

渲染进程

每个 Electron 的页面都在运行着自己的进程,这样的进程我们称之为渲染进程,与主进程不同,它能存在多个(注:一个 Electron 应用只能有一个主进程)并且是相互独立的。它通常被命名为index.html。它们就像典型的 HTML 文件,但在 Electron 中,它们能获取完整的 Node API 特性,因此,这也是它与其它浏览器不同的地方。

  • 渲染页面:能够使用传统的HTML和CSS来设计界面,可以用JavaScript对DOM进行操作。
  • Node组件:可以使用Node提供的自带的组件以及第三方的Node组件包。
  • 使用自定义的DOM标签:可以在页面中使用Electron增加的webview标签。
     两种进程  - 图3

主进程与渲染进程间通信(IPC)

在 Electron 中,每个进程独立享有自己的 JavaScript 内容,彼此之间无法直接访问。当我们需要在进程之间传递数据时,就需要使用进程间通信(IPC),IPC就为此而生,它提供了进程间的通讯,但它只能在主进程与渲染进程之间传递信息。在 Electron中提供几种方法用于主进程和渲染进程之间的通讯,像 ipcRendereripcMain 模块用于发送消息,remote 模块用于 RPC 方式通讯。

  • 主进程给渲染进程发送异步消息:在主进程中通过查找某个窗口的WebContents实例,并通过实例方法send就可以实现主进程向渲染进程发送异步消息了。查看更多 webContents.send
  • 渲染进程给主进程发送异步消息:通过ipcRenderer.send方法就可以向主进程发送异步消息,也可以发送任意参数,参数会被JSON序列化。主进程通过使用 ipcMain 模块来监听 channel ,从而处理消息。查看更多Sending-Messages
  • 渲染进程给主进程发送同步消息:通过ipcRenderer.sendSync方法就可以向主进程发送同步消息,主进程通过使用 ipcMain 模块来监听 channel,从而处理消息,通过 event.returnValue 来响应。查看更多Sending-Messages
     两种进程  - 图4

RPC

remote 模块提供了一种在渲染进程(网页)和主进程之间进行进程间通讯(IPC)的简便途径。Electron中, 与GUI相关的模块(如 dialog , menu 等)只存在于主进程,而不在渲染进程中 。为了能从渲染进程中使用它们,需要用 ipc 模块来给主进程发送进程间消息。使用 remote 模块,可以调用主进程对象的方法,而无需显式地发送进程间消息,这类似于 Java 的 RMI。

注意: 从主进程访问渲染进程可以使用webContents.executeJavascript

渲染进程间共享数据

每个 Electron 渲染进程是独立的,因此它们不会互相影响,但这也带来了一个问题:它们直接如何相互通讯呢?

下面有 3种方式进行通讯:

  • Storage API:对某个标签页的 localStorage/sessionStorage 对象进行增删改时,其他标签页能通过 window.storage 事件监听到。
  • IndexedDB:IndexedDB 是一个为了能够在客户端存储可观数量的结构化数据,并且在这些数据上使用索引进行高性能检索的 API。
  • 通过主进程作为中转站:将数据存在主进程的某个全局变量中,然后在多个渲染进程中使用 remote 模块来访问它。查看更多Share Data

渲染进程间交互

在上面已经解决了渲染进程之间的数据共享以及主进程与渲染进程的数据共享问题,但是这种方式没有解决渲染进程之间的事件通知问题,如何解决呢?

下面有 2种方式进行通讯:

  • 通过主进程作为中转站:在主进程通过ipcMain监听所有的页面发送过来的消息,根据消息的channel和目标窗口的id或者title等标识,再通过webContents.send来发送给目标窗口消息。

  • 通过Node的net组件创建socket或者命名管道:在某个页面进程中创建命名管道的服务端,在其他页面就可以连接该服务端的管道,进行数据的收发,查看更多Net

  1. //Page A
  2. var net = require('net');
  3. var PIPE_NAME = "SampleServer";
  4. var PIPE_PATH = "\\\\.\\pipe\\" + PIPE_NAME;
  5. var L = console.log;
  6. // == Server part == //
  7. var server = net.createServer(function(stream) {
  8. L('Server: on connection');
  9. stream.on('data', function(c) {
  10. L('Server: on data:', c.toString());
  11. });
  12. stream.on('end', function() {
  13. L('Server: on end');
  14. // server.close();
  15. });
  16. stream.write('Take it easy!');
  17. });
  18. server.on('close',function(){
  19. L('Server: on close');
  20. });
  21. server.listen(PIPE_PATH,function(){
  22. L('Server: on listening');
  23. });
  1. //Page B,C....
  2. // == Client part == //
  3. var client = net.connect(PIPE_PATH, function() {
  4. L('Client: on connection');
  5. })
  6. client.on('data', function(data) {
  7. L('Client: on data:', data.toString());
  8. client.end('Thanks!');
  9. });
  10. client.on('end', function() {
  11. L('Client: on end');
  12. })