Electron系列文章-主进程与渲染进程

avatar
SugarTurboS Club @SugarTurboS

本文主要讲解上一章节提到的主进程与渲染进程的概念,以及它们是如何被使用的

如果对文章的内容有任何疑问或吐槽,请直接在下方评论,大家共同学习和改进

阅读时间:约4min

主进程与渲染进程

我们来回顾一下,程序目录结构章节中所给出的基本目录结构

app----------------------------应用程序代码目录
├─main.js----------------------程序启动入口,主进程
├─common-----------------------通用模块
├─log--------------------------日志模块
├─config-----------------------配置模块
├─ipc--------------------------进程间模块
├─appNetwork-------------------应用通信模块
└─browserWindows---------------窗口管理,渲染进程
    ├─components---------------通用组件模块
    ├─store--------------------数据共享模块
    ├─statics------------------静态资源模块
    └─src----------------------窗口业务模块
        ├─窗口A----------------窗口
        └─窗口B----------------窗口

在上面的目录结构中,main.js就是我们所说的主进程。而通过browserWindows目录下窗口文件创建的进程,我们称之为渲染进程。渲染进程需要通过主进程来创建,并被主进程所管理。这里大家可能会有疑问了,什么是进程?为什么要分主进程和渲染进程呢?

进程的概念

计算机进程相关的知识在搜索引擎中可以搜到很多,我们这里不做过多的讲解。读者可以把进程理解为操作系统管理应用程序的基本单位,每个进程之间的资源是不能直接共享的。打开操作系统的任务管理器,我们可以看到当前操作系统都有那些进程,如下图

主进程

回顾以往的web开发,我们的代码,无论是HTML、CSS还是Javascript,都是运行在浏览器的沙盒中的,我们无法越过浏览器的权限访问系统本身的资源,代码的能力被限制在了浏览器中。浏览器之所以这么做,是为了安全的考虑。设想一下,我们在使用浏览器的时候,会打开各式各样不同来源的网站,如果JavaScript代码有能力访问并操作本地操作系统的资源,那将是多么可怕的事情。你在某天不小心打开了一个恶意的网站,可能你存储在硬盘上的文件就被偷走了(都用不着去修电脑)。

但我们要开发的是桌面应用程序,如果无法访问到本地的资源肯定是不行的。Electron将nodejs巧妙的融合了进来,让nodejs作为整个程序的管家。管家拥有较高的权限,可以访问和操作本地资源,使用原本在浏览器中不提供的高级API。同时管家也管理着渲染进程窗口的创建和销毁。所以,我们将这个管家称之为主进程。在使用Electron开发的程序中,会使用main.js作为程序的主入口,该文件内代码执行的内容,就是主进程中执行的内容。

下面我们一起来看看主进程中一般都做些什么。

//main.js

var electron      = require('electron');
var app           = require('electron').app;

//初始化
app.on('ready', function(){

    try{      
        //app ready
    }catch(err){
        log.error(err);
    }
});

上面是一个最简单的main.js例子。在例子中,我们首先引入了electronelectron.app模块。electron.app对象控制着整个程序的生命周期,在这我们只注册了生命周期中的ready事件,当ready事件被触发的时候,表示整个程序已经初始化完毕,可以开始进行创建窗口等业务逻辑了。

在现实的应用中,主进程的代码远比上面的例子复杂的多。有许多需求特性的实现都是在主进程中完成,下面举个例子进行讲解。

程序互斥

在某些场景中,应用程序被要求在系统中只能同时开启一个。所以当某个应该已经在系统中运行时,如果再次双击应用icon,程序应该将自己退掉,或者是把之前打开的程序退掉。

(function(){

  app.makeSingleInstance(singleInstanceCallBack);

  function singleInstanceCallBack(commandLine, workingDirectory){

    console.log('command line: ', commandLine);
    console.log('working directory: ', workingDirectory);

    app.exit(0);        
  }
})();

这里使用的是app.makeSingleInstanceapi实现的这个功能。singleInstanceCallBack函数是当第二个实例启动时,第一个实例执行的回调,所以singleInstanceCallBack里面使用的app实际上指向的是先打开的实例(刚接触这个api的时候确实有点绕),上面的代码中,singleInstanceCallBack执行时,调用了app.exit方法退出了先打开的程序。那我们如何去判断当前是第二个实例呢?其实,makeSingleInstance的返回值是一个boolean类型的值,为true时表示当前是第二个实例。如果需求是想让第二个实例退出,则在当前的作用域中直接调用app.exit即可。

渲染进程

Electron集成了Chromium来展示窗口界面,窗口中所看到的内容使用的都是HTML渲染出来的。 Chromium本身是多进程渲染页面的架构(在默认情况下,Chromium的默认策略是对每一个tab新开一个进程,以确保每个页面是独立且互不影响的。避免一个页面的崩溃导致全部页面无法使用),所以Electron在展示窗口时,也会使用到Chromium的多进程架构。而这种多进程渲染架构在Electron中,就被称之为渲染进程(render process)。

在Electron中,每创建一个新的窗口,都是一个独立的进程。

//main.js
const {BrowserWindow} = require('electron');
const window1 = new BrowserWindow({width: 200, height: 200})
const window2 = new BrowserWindow({width: 200, height: 200})

window1.loadURL('https://baidu.com');
window2.loadURL(`file://${__dirname}/index.html`)

在上面的代码中,我们创建了2个窗口,window1window2。这两个窗口就是互相独立的进程。window1窗口内容指向的是百度首页,window2窗口内容指向的是我们自己开发的html页面。在index.html中,我们可以像开发正常的web网页一样,开发窗口内容。以下是index.html的一个示例

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1,user-scalable=0">
    <title>main page</title>
</head>
<body>
    <div id="app">Hello World</div>
    <script type="text/javascript" src="mainPage.js"></script>
</body>
</html>

在介绍主进程的篇幅中,我们说到只有主进程才有获取本地资源的权限。实际场景中,有时需要将主进程中获取的本地数据在窗口中展示出来,该怎么实现呢?

在下一章,我们会重点讲解主进程与渲染进程之间如何通信的知识。