为了给您提供更优质的服务,请您先完善以下信息:
确认提交

扫码关注

爱数技术支持中心公众号

请选择:

请选择咨询类型

AnyShare
AnyBackUp
AnyRobot

扫码关注

爱数技术支持中心公众号

contact us

提交成功!

我们将在 24 小时之内联系你。

性能爆表
AnyShare
如何购买
我想先试用
我已是Anyshare 客户
AnyRobot
如何购买
购买 AnyRobot 订阅服务
我已是 AnyRobot 客户
一对一在线咨询
我是 AnyRobot 新客户
一对一在线咨询

爱数博客

全部 AnyBackup AnyShare AnyRobot AnyDATA AnyFabric DIP

从架构出发探究AnyShare客户端所用技术Electron的运行原理

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

使用Electron开发的产品很多,比如VSCode、WhatsApp、Microsoft Teams,此外,AnyShare的客户端也是采用Electron技术开发。


 
AnyShare客户端截图
 基于Electron实现的产品

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

Electron通过集成浏览器内核,使用Web技术来实现不同平台下的渲染,并结合了 Chromium 、Node.js 和用于调用系统本地功能的 API 三大板块:
 
  • Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为Mac,Windows和Linux系统下的应用;
  • Chromium 为 Electron 提供强大的 UI 渲染能力,由于 Chromium 本身跨平台,因此无需考虑代码的兼容性;
  • Chromium 并不具备原生 GUI 的操作能力,因此 Electron 内部集成 Node.js,编写 UI 的同时也能够调用操作系统的底层 API,例如 path、fs、crypto 等模块;
  • 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客户端?

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

1、在React项目目录下安装Electron:
 
npm install electron

2、修改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"
    ]
  }
}

3、React项目根目录下新建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.loadURL("http://localhost:3000/")
}
 
app.whenReady().then(() => {
  createWindow();
 
  app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});
 
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
})

4、打开两个终端,一个运行React,另一个执行以下命令:
 
npm run electron-start

这样一个Electron简单的桌面客户端就运行起来了

请就本文对您的益处进行评级:

标签

知识汇

相关文章

热门标签

版本发布 在线教学
ai-assistant
chat
support
trial
需求助手 (内容由 AI 大模型生成,请仔细甄别)