早期桌面应用的开发主要借助原生 C/C++ API 进行,由于需要反复经历编译过程,且无法分离界面 UI 与业务代码,开发调试极为不便。后期出现的 QT 和 WPF 在一定程度上解决了界面代码分离和跨平台的问题,却依然无法避免较长时间的编译过程。近几年伴随互联网行业的迅猛发展,尤其是 NodeJS、Chromium 这类基于 W3C 标准开源应用的不断涌现,原生代码与 Web 浏览器开发逐步走向融合,Electron 在这种背景下诞生。

基于Electron实现的产品

Electron提供了丰富的本地(操作系统)API,使你能够使用纯JavaScript来创建桌面应用程序。与其它各种的Node.js运行时不同的是Electron专注于桌面应用程序而不是Web服务器。

Electron通过集成浏览器内核,使用Web技术来实现不同平台下的渲染,并结合了 Chromium 、Node.js 和用于调用系统本地功能的 API 三大板块。

  1. Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为Mac,Windows和Linux系统下的应用;
  2. Chromium 为 Electron 提供强大的 UI 渲染能力,由于 Chromium 本身跨平台,因此无需考虑代码的兼容性。
  3. Chromium 并不具备原生 GUI 的操作能力,因此 Electron 内部集成 Node.js,编写 UI 的同时也能够调用操作系统的底层 API,例如 path、fs、crypto 等模块。
  4. Native API 为 Electron 提供原生系统的 GUI 支持,借此 Electron 可以调用原生应用程序接口。

总结起来,Chromium 负责页面 UI 渲染,Node.js 负责业务逻辑,Native API 则提供原生能力和跨平台。

上面粗略讲解了 Electron 的跨端原理,下面我们来深究一下。

Chromium 架构

Chromium 是 Chrome 的开源版,也是一个浏览器,Google Chrome 浏览器正是基于它。

Electron底层基于Chromium,Chromium的设计理念是基于多进程的,每个Tab都是一个独立的进程,称之为Renderer Process,有多少个Tab就有多少个Renderer Process。(图源: Chromium 官网)

另外还有一个,有且只有一个的主进程,称之为Main Process(浏览器整体的Window),它负责其他众多Renderer Process的创建、分配,还有其他众多整体上的控制。

因此如果有一个Tab崩溃的话,不会影响到其他的Tab,浏览器可以继续运行。

Chromium 的多进程模式主要由三部分组成: 浏览器端(Browser)、渲染器端(Render)、浏览器与渲染器的通信方式(IPC)

1.浏览器进程

浏览器进程 Browser 只有一个,当 Chrome 打开时,进程启动。浏览器为每个渲染进程维护对应的 RenderProcessHost,负责浏览器与渲染器的交互。RenderViewHost 则是与 RenderView 对象进行交互,渲染网页的内容。浏览器与渲染器通过 IPC 进行通信。

2.渲染进程管理

每个渲染进程都有一个全局 RenderProcess 对象,可以管理其与父浏览器进程之间的通信,并维护其全局状态。

3.view 管理

每个渲染器可以维护多个 RenderView 对象,当新开标签页或弹出窗口后,渲染进程就会创建一个 RenderView,RenderView 对象与它在浏览器进程中对应的 RenderViewHost 和 Webkit 嵌入层通信,渲染出网页内容(这里是我们日常主要关注的地方)。

Electron 架构解析

Electron 架构参考了 Chromium 的多进程架构模式,即将主进程和渲染进程隔离,并且在 Chromium 多进程架构基础上做一定扩展。

将上面复杂的 Chromium 架构简化:

Chromium 运行时由一个 Browser Process,以及一个或者多个 Renderer Process 构成。Renderer Process 负责渲染页面 Web ,Browser Process 负责管理各个 Renderer Process 以及其他功能(菜单栏、收藏夹等)

下面我们看一下 Electron 架构有那些变化?

Electron 架构中仍然使用了 Chromium 的 Renderer Process 渲染界面,Renderer Process 可以有多个,互相独立不干扰。由于 Electron 为其集成了 Node 运行时,Renderer Process 还可以调用 Node API。

相较于 Chromium 架构,Electron 对 Browser 进程做了很多改动,将其更改名 Main Process,每个应用程序只能有一个主进程,主进程位于 Node.js 下运行,因此其可以调用系统底层功能,其主要负责:渲染进程的创建;系统底层功能及原生资源的调用;应用生命周期的控制(包裹启动、推出以及一些事件监听),可以把它看做页面和计算机沟通的桥梁。

经过上面的分析,Electron 多进程的系统架构可以总结为下图:

可以发现,主进程和渲染进程都集成了 Native API 和 Node.js,渲染进程还集成 Chromium 内核,成功实现跨端开发。

在Electron中,GUI组件仅在主进程可用,在渲染进程中不可用。那如果想要在渲染进程中使用GUI组件,势必需要和主进程进行通信。ipc模块就是用来实现主进程和渲染进程之间的通信。在主进程中使用ipcMain模块进行对渲染进程的通信进行控制和处理。而在渲染进程中,则使用ipcRenderer模块,来向主进程发送消息或者接受主进程的回应。

Node 与 Chromium

没有Chromium就没有V8(Chromium内置的高性能JavaScript执行引擎),没有V8就没有Node.js。Chromium的高性能并不单单是多进程架构的功劳,V8引擎也居功甚伟,V8引擎以超高性能执行JavaScript脚本著称,Node.js的作者也是因为这一点才决定封装V8,把JavaScript程序员的战场引向客户端和服务端。

Node 的事件循环与浏览器的事件循环有明显不同,Chromium 既然是 Chrome 的实验版,自然与浏览器实现相同。

Node 的事件循环基于 libuv 实现,而 Chromium 基于 message bump 实现。主线程只能同时运行一个事件循环,因此需要将两个完全不同的事件循环整合起来。

有两种解决方案:

  • 使用 libuv 实现 message bump 将 Chromium 集成到 Node.js
  • 将 Node.js 集成到 Chromium

Electron 最初的方案是第一种,使用 libuv 实现 message bump,但不同的 OS 系统 GUI 事件循环差异很大,例如 mac 为 NSRunLoop,Linux 为 glib,实现过程特别复杂,资源消耗和延迟问题也无法得到有效解决,最终放弃了第一种方案。

Electron 第二次尝试使用小间隔的定时器来轮询 GUI 事件循环,但此方案 CPU 占用高,并且 GUI 响应速度慢。

后来 libuv 引入了 backend_fd 概念,backend_fd 轮询事件循环的文件描述符,因此 Electron 通过轮询 backend_fd 来得到 libuv 的新事件实现 Node.js 与 Chromium 事件循环的融合(第二种方案)。

下面这张 PPT 完美的描述了上述过程(图源:Electron: The Event Loop Tightrope - Shelley Vohr | JSHeroes 2019)

如何开发?

开发Electron应用的方式有很多,下面以React为例做个简单的说明:

1. 热调试

在React项目目录下安装Electron

npm install electron

修改package.json文件,增加或将已有的main属性值修改为main.js,在scriptes中添加"electron-start": "electron .",最终配置文件如下:

{"name": "electron-react","version": "0.1.0","main": "main.js","private": true,"dependencies": {"@testing-library/jest-dom": "^5.16.5","@testing-library/react": "^13.3.0","@testing-library/user-event": "^13.5.0","electron": "^20.0.3","react": "^18.2.0","react-dom": "^18.2.0","react-scripts": "5.0.1","web-vitals": "^2.1.4"},"scripts": {"start": "react-scripts start","build": "react-scripts build","test": "react-scripts test","eject": "react-scripts eject","electron-start": "electron ."},"eslintConfig": {"extends": ["react-app","react-app/jest"]},"browserslist": {"production": [">0.2%","not dead","not op_mini all"],"development": ["last 1 chrome version","last 1 firefox version","last 1 safari version"]}
}

打开main.js,将

const { app, BrowserWindow, globalShortcut } = require("electron");
const path = require("path");function createWindow() {const win = new BrowserWindow({width: 800,height: 600,webContents: {openDevTools: true, //不想要控制台直接把这段删除}});win.loadFile("index.html");
}app.whenReady().then(() => {createWindow();app.on("activate", () => {if (BrowserWindow.getAllWindows().length === 0) {createWindow();}});
});app.on("window-all-closed", () => {if (process.platform !== "darwin") {app.quit();}
})

中的

win.loadFile("index.html");

修改为

win.loadURL("http://localhost:3000/")

打开两个终端,一个运行React

npm start

另一个执行

npm run electron-start

程序运行正常

从架构出发探究Electron运行原理相关推荐

  1. Apollo架构体系、Apollo运行原理、Apollo配置中心简单介绍(一)

    笔者在工作中遇到如下问题,随着程序功能越多,配置文件不断增加,一些功能的开关.服务器地址.接口地址.不同环境的一些配置文件不同,这些在每次发布不同环境.更新项目时都比较繁琐,后来学习微服务时接触到了S ...

  2. spark on yarn 完全分布式_Spark编程笔记(1)-架构基础与运行原理

    引言 根据IBM前首席执行官郭士纳的观点,IT领域每隔十五年就会迎来一 次重大变革 .当前我们正处于第三次信息浪潮(2010年前后),物联网.云计算和大数据技术突飞猛进. 信息爆炸是我们当前所需要解决 ...

  3. 学习 launch-editor 源码整体架构,探究 vue-devtools「在编辑器中打开组件」功能实现原理...

    1. 前言 你好,我是若川[1],微信搜索「若川视野」关注我,专注前端技术分享,一个愿景是帮助5年内前端开阔视野走向前列的公众号.欢迎加我微信ruochuan12,长期交流学习. 这是学习源码整体架构 ...

  4. LoadRunner系统架构简介与运行原理

    1.LoadRunner系统架构简介 LoadRunner是通过创建虚拟用户来代替真实实际用户来操作客户端软件比如Internet Explorer,来向IIS.Apache等Web服务器发送HTTP ...

  5. Serverless 实战 —— Serverless 的运行原理与组件架构

    Serverless 的运行原理与组件架构 本文重点探讨下开发者使用 Serverless 时经常遇到的一些问题,以及如何解决 过去一年,我们和大量 Serverless 用户进行了线上和线下的交流, ...

  6. Spark SQL运行原理和架构

    一 Spark SQL运行架构 Spark SQL对SQL语句的处理和关系型数据库类似,即词法/语法解析.绑定.优化.执行.Spark SQL会先将SQL语句解析成一棵树,然后使用规则(Rule)对T ...

  7. spark基础之spark sql运行原理和架构

    一 Spark SQL运行架构 Spark SQL对SQL语句的处理和关系型数据库类似,即词法/语法解析.绑定.优化.执行.Spark SQL会先将SQL语句解析成一棵树,然后使用规则(Rule)对T ...

  8. Nginx架构篇(一)动态网站架构、LNMP、FastCGI、nginx+fastcgi运行原理

    一.动态网站架构 资源文件识别 语言识别 框架识别 index.php 开源的php Window/Linux+nginx+php+mysql index.py 开源的python Window/Li ...

  9. php的主要架构,php运行原理与基本结构

    一.php运行原理 二.post与get请求的区别 GET请求 请求参数以键值对的方式,附加到url地址上,称为查询字符串,用?号与当前脚本分隔 url格式:'index.php?name=peter ...

最新文章

  1. python需要的数学基础训练答案_新编基础训练数学答案
  2. linux 踢出其他用户,Linux查看当前登录用户并踢出用户
  3. Shell中的while语句
  4. 如何攻击Java Web应用【转载】
  5. ASP.NET学习笔记之操作过滤器
  6. 就业阶段-java语言进价_day04
  7. 微软总裁比尔.盖茨给即将走出学校、踏入社会的青年一代下列11点忠告
  8. 【Spark】Spark Streaming的back pressure
  9. 【C++实现】HeadFirst策略模式设计模式
  10. Ansible条件测试
  11. FTP服务端管理软件:Serv-U和filezilla
  12. Lucene中的各种consumer
  13. Atitit.并发编程原理与概论 attilax总结
  14. 处理器仿存带宽_《CPU与内存的带宽搭配》
  15. React-Pdf使用过程心得
  16. C51单片机教程之工程建立
  17. 微信开发自带版本管理的使用(图文)---推送,抓取,拉取,贮藏
  18. 推荐几款好用的Mysql图形化管理工具
  19. 网站测压机器人_压力测试工具(bots)
  20. React双向数据绑定原理---受控组件

热门文章

  1. 【计算机考研408-计算机网络-教书匠视频笔记】主机访问浏览器的全部过程
  2. 3月28日出生的赵姓女孩根据生辰八字取名字有内涵诗意的名字
  3. 2022-2027年中国直升机行业市场调研及未来发展趋势预测报告
  4. android FileDownloader
  5. H.265流媒体播放器EasyPlayer手机端播放画面出现强制拉伸现象的解决办法
  6. LVS负载均衡群集架构(二):DR模式群集部署+keepalived
  7. Ardunio编程时务必注意定义引脚的输出方式
  8. 从2022年自动驾驶融资情况看行业风向标
  9. 拼题A基础篇 30 求整数的位数及其各位数字之和
  10. 借钱给朋友,将以失去友情当作利息《塔木德》